Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
AbstractAbstractFileNode.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datamodel;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.lang.ref.WeakReference;
24 import java.text.MessageFormat;
25 import java.util.ArrayList;
26 import java.util.EnumSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.logging.Level;
31 import java.util.stream.Collectors;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.openide.nodes.Sheet;
35 import org.openide.util.NbBundle;
36 import org.openide.util.WeakListeners;
51 import static org.sleuthkit.autopsy.datamodel.Bundle.*;
61 import org.sleuthkit.datamodel.AbstractFile;
62 import org.sleuthkit.datamodel.Content;
63 import org.sleuthkit.datamodel.ContentTag;
64 import org.sleuthkit.datamodel.Tag;
65 import org.sleuthkit.datamodel.TskCoreException;
69 import org.sleuthkit.datamodel.Score;
70 import org.sleuthkit.datamodel.VirtualDirectory;
71 
77 public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends AbstractContentNode<T> {
78 
79  private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName());
80 
81  private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
83  private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(CONTENT_CHANGED);
84 
88  AbstractAbstractFileNode(T abstractFile) {
89  super(abstractFile);
90  String ext = abstractFile.getNameExtension();
91  if (StringUtils.isNotBlank(ext)) {
92  ext = "." + ext;
93  // If this is an archive file we will listen for ingest events
94  // that will notify us when new content has been identified.
95  if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
97  }
98  }
99 
100  // Add listener if this is the carved files base directory.
101  if (VirtualDirectory.NAME_CARVED.equals(abstractFile.getName())) {
103  }
104 
105  try {
106  //See JIRA-5971
107  //Attempt to cache file path during construction of this UI component.
108  this.content.getUniquePath();
109  } catch (TskCoreException ex) {
110  logger.log(Level.SEVERE, String.format("Failed attempt to cache the "
111  + "unique path of the abstract file instance. Name: %s (objID=%d)",
112  this.content.getName(), this.content.getId()), ex);
113  }
114 
116  backgroundTasksPool.submit(new TranslationTask(
117  new WeakReference<>(this), weakPcl));
118  }
119 
120  // Listen for case events so that we can detect when the case is closed
121  // or when tags are added.
123  }
124 
134  @Override
135  protected void finalize() throws Throwable {
136  super.finalize();
137  removeListeners();
138  }
139 
140  private void removeListeners() {
143  }
144 
145  private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
146  String eventType = evt.getPropertyName();
147 
148  // Is this a content changed event?
149  if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
150  if ((evt.getOldValue() instanceof ModuleContentEvent) == false) {
151  return;
152  }
153  ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue();
154  if ((moduleContentEvent.getSource() instanceof Content) == false) {
155  return;
156  }
157  Content newContent = (Content) moduleContentEvent.getSource();
158 
159  // Does the event indicate that content has been added to *this* file?
160  if (getContent().getId() == newContent.getId()) {
161  // If so, refresh our children.
162  try {
163  // We only want to refresh our parents children if we are in the
164  // data sources branch of the tree. The parent nodes in other
165  // branches of the tree (e.g. File Types and Deleted Files) do
166  // not need to be refreshed.
167  if (VirtualDirectory.NAME_CARVED.equals(getContent().getName())) {
168  // If the current node is the carved files directory, we need to refresh it
170  } else {
171  // Otherwise we need to refresh the parent node
172  BaseChildFactory.post(getParentNode().getName(), new RefreshKeysEvent());
173  }
174  } catch (NullPointerException ex) {
175  // Skip
176  } catch (NoSuchEventBusException ex) {
177  logger.log(Level.WARNING, "Failed to post key refresh event", ex); //NON-NLS
178  }
179  }
180  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
181  if (evt.getNewValue() == null) {
182  // case was closed. Remove listeners so that we don't get called with a stale case handle
183  removeListeners();
184  }
185  /*
186  * No need to do any asynchrony around tag added, deleted or CR
187  * change events, they are so infrequent and user driven that we can
188  * just keep a simple blocking approach, where we go out to the
189  * database ourselves.
190  */
191  } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
193  if (event.getAddedTag().getContent().equals(content)) {
194 
195  Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription();
196  Score value = scorePropAndDescr.getLeft();
197  String descr = scorePropAndDescr.getRight();
198  List<CorrelationAttributeInstance> listWithJustFileAttr = new ArrayList<>();
200  if (corrInstance != null) {
201  listWithJustFileAttr.add(corrInstance);
202  }
203  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), descr, value),
204  new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(getAllTagsFromDatabase(), listWithJustFileAttr))
205  );
206  }
207  } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
209  if (event.getDeletedTagInfo().getContentID() == content.getId()) {
210  List<Tag> tags = getAllTagsFromDatabase();
211  Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription();
212  Score value = scorePropAndDescr.getLeft();
213  String descr = scorePropAndDescr.getRight();
214  List<CorrelationAttributeInstance> listWithJustFileAttr = new ArrayList<>();
216  if (corrInstance != null) {
217  listWithJustFileAttr.add(corrInstance);
218  }
219  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), descr, value),
220  new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(getAllTagsFromDatabase(), listWithJustFileAttr))
221  );
222  }
223  } else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
225  if (event.getContentID() == content.getId()) {
226  List<CorrelationAttributeInstance> listWithJustFileAttr = new ArrayList<>();
228  if (corrInstance != null) {
229  listWithJustFileAttr.add(corrInstance);
230  }
231  updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(getAllTagsFromDatabase(), listWithJustFileAttr)));
232  }
233  } else if (eventType.equals(NodeSpecificEvents.TRANSLATION_AVAILABLE.toString())) {
234  this.setDisplayName(evt.getNewValue().toString());
235  //Set the tooltip
236  this.setShortDescription(content.getName());
237  updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName()));
238  } else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString()) && !UserPreferences.getHideSCOColumns()) {
239  SCOData scoData = (SCOData) evt.getNewValue();
240  if (scoData.getScoreAndDescription() != null) {
241  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
242  }
243  if (scoData.getComment() != null) {
244  updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment()));
245  }
246  if (scoData.getCountAndDescription() != null) {
247  updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
248  }
249  }
250  };
259  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
260 
261  /*
262  * This is called when the node is first initialized. Any new updates or
263  * changes happen by directly manipulating the sheet. That means we can fire
264  * off background events everytime this method is called and not worry about
265  * duplicated jobs.
266  */
267  @Override
268  protected synchronized Sheet createSheet() {
269  Sheet sheet = new Sheet();
270  Sheet.Set sheetSet = Sheet.createPropertiesSet();
271  sheet.put(sheetSet);
272 
273  //This will fire off fresh background tasks.
274  List<NodeProperty<?>> newProperties = getProperties();
275  newProperties.forEach((property) -> {
276  sheetSet.put(property);
277  });
278 
279  return sheet;
280  }
281 
282  @NbBundle.Messages({"AbstractAbstractFileNode.nameColLbl=Name",
283  "AbstractAbstractFileNode.originalName=Original Name",
284  "AbstractAbstractFileNode.createSheet.score.name=S",
285  "AbstractAbstractFileNode.createSheet.comment.name=C",
286  "AbstractAbstractFileNode.createSheet.count.name=O",
287  "AbstractAbstractFileNode.locationColLbl=Location",
288  "AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time",
289  "AbstractAbstractFileNode.changeTimeColLbl=Change Time",
290  "AbstractAbstractFileNode.accessTimeColLbl=Access Time",
291  "AbstractAbstractFileNode.createdTimeColLbl=Created Time",
292  "AbstractAbstractFileNode.sizeColLbl=Size",
293  "AbstractAbstractFileNode.flagsDirColLbl=Flags(Dir)",
294  "AbstractAbstractFileNode.flagsMetaColLbl=Flags(Meta)",
295  "AbstractAbstractFileNode.modeColLbl=Mode",
296  "AbstractAbstractFileNode.useridColLbl=UserID",
297  "AbstractAbstractFileNode.groupidColLbl=GroupID",
298  "AbstractAbstractFileNode.metaAddrColLbl=Meta Addr.",
299  "AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.",
300  "AbstractAbstractFileNode.typeDirColLbl=Type(Dir)",
301  "AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)",
302  "AbstractAbstractFileNode.knownColLbl=Known",
303  "AbstractAbstractFileNode.md5HashColLbl=MD5 Hash",
304  "AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash",
305  "AbstractAbstractFileNode.objectId=Object ID",
306  "AbstractAbstractFileNode.mimeType=MIME Type",
307  "AbstractAbstractFileNode.extensionColLbl=Extension"})
309 
310  NAME(AbstractAbstractFileNode_nameColLbl()),
311  ORIGINAL_NAME(AbstractAbstractFileNode_originalName()),
312  SCORE(AbstractAbstractFileNode_createSheet_score_name()),
313  COMMENT(AbstractAbstractFileNode_createSheet_comment_name()),
314  OCCURRENCES(AbstractAbstractFileNode_createSheet_count_name()),
315  LOCATION(AbstractAbstractFileNode_locationColLbl()),
316  MOD_TIME(AbstractAbstractFileNode_modifiedTimeColLbl()),
317  CHANGED_TIME(AbstractAbstractFileNode_changeTimeColLbl()),
318  ACCESS_TIME(AbstractAbstractFileNode_accessTimeColLbl()),
319  CREATED_TIME(AbstractAbstractFileNode_createdTimeColLbl()),
320  SIZE(AbstractAbstractFileNode_sizeColLbl()),
321  FLAGS_DIR(AbstractAbstractFileNode_flagsDirColLbl()),
322  FLAGS_META(AbstractAbstractFileNode_flagsMetaColLbl()),
323  MODE(AbstractAbstractFileNode_modeColLbl()),
324  USER_ID(AbstractAbstractFileNode_useridColLbl()),
325  GROUP_ID(AbstractAbstractFileNode_groupidColLbl()),
326  META_ADDR(AbstractAbstractFileNode_metaAddrColLbl()),
327  ATTR_ADDR(AbstractAbstractFileNode_attrAddrColLbl()),
328  TYPE_DIR(AbstractAbstractFileNode_typeDirColLbl()),
329  TYPE_META(AbstractAbstractFileNode_typeMetaColLbl()),
330  KNOWN(AbstractAbstractFileNode_knownColLbl()),
331  MD5HASH(AbstractAbstractFileNode_md5HashColLbl()),
332  SHA256HASH(AbstractAbstractFileNode_sha256HashColLbl()),
333  ObjectID(AbstractAbstractFileNode_objectId()),
334  MIMETYPE(AbstractAbstractFileNode_mimeType()),
335  EXTENSION(AbstractAbstractFileNode_extensionColLbl());
336 
337  final private String displayString;
338 
339  private AbstractFilePropertyType(String displayString) {
340  this.displayString = displayString;
341  }
342 
343  @Override
344  public String toString() {
345  return displayString;
346  }
347  }
348 
352  private List<NodeProperty<?>> getProperties() {
353  List<NodeProperty<?>> properties = new ArrayList<>();
354  properties.add(new NodeProperty<>(NAME.toString(), NAME.toString(), NO_DESCR, getContentDisplayName(content)));
355  /*
356  * Initialize an empty place holder value. At the bottom, we kick off a
357  * background task that promises to update these values.
358  */
359 
361  properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, ""));
362  }
363 
364  // Create place holders for S C O
366  properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, ""));
367  properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, ""));
369  properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, ""));
370  }
371  // Get the SCO columns data in a background task
372  backgroundTasksPool.submit(new GetSCOTask(
373  new WeakReference<>(this), weakPcl));
374  }
375 
376  properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getMtime())));
377  properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getCtime())));
378  properties.add(new NodeProperty<>(ACCESS_TIME.toString(), ACCESS_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getAtime())));
379  properties.add(new NodeProperty<>(CREATED_TIME.toString(), CREATED_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getCrtime())));
380  properties.add(new NodeProperty<>(SIZE.toString(), SIZE.toString(), NO_DESCR, content.getSize()));
381  properties.add(new NodeProperty<>(FLAGS_DIR.toString(), FLAGS_DIR.toString(), NO_DESCR, content.getDirFlagAsString()));
382  properties.add(new NodeProperty<>(FLAGS_META.toString(), FLAGS_META.toString(), NO_DESCR, content.getMetaFlagsAsString()));
383  properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName()));
384  properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
385  properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash())));
386  properties.add(new NodeProperty<>(SHA256HASH.toString(), SHA256HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getSha256Hash())));
387  properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType())));
388  properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension()));
389 
390  return properties;
391  }
392 
402  @NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
403  @Deprecated
404  protected void addTagProperty(Sheet.Set sheetSet) {
405  List<ContentTag> tags = getContentTagsFromDatabase();
406  sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
407  NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
408  .distinct()
409  .collect(Collectors.joining(", "))));
410  }
411 
422  @Deprecated
423  protected static String getHashSetHitsCsvList(AbstractFile file) {
424  try {
425  return StringUtils.join(file.getHashSetNames(), ", ");
426  } catch (TskCoreException tskCoreException) {
427  logger.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
428  return "";
429  }
430  }
431 
432  @NbBundle.Messages({
433  "AbstractAbstractFileNode.createSheet.count.displayName=O",
434  "AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
435  "# {0} - occurrenceCount",
436  "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurrences of the MD5 correlation value"})
437  @Override
438  protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attributeInstance, String defaultDescription) {
439  Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
440  String description = defaultDescription;
441  try {
442  //don't perform the query if there is no correlation value
443  if (attributeInstance != null && StringUtils.isNotBlank(attributeInstance.getCorrelationValue())) {
445  description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
446  } else if (attributeInstance != null) {
447  description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
448  }
449  } catch (CentralRepoException ex) {
450  logger.log(Level.SEVERE, "Error getting count of datasources with correlation attribute", ex);
452  logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
453  }
454  return Pair.of(count, description);
455  }
456 
457  @NbBundle.Messages({
458  "AbstractAbstractFileNode.createSheet.comment.displayName=C"})
459  @Override
460  protected HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) {
462  for (Tag tag : tags) {
463  if (!StringUtils.isBlank(tag.getComment())) {
464  //if the tag is null or empty or contains just white space it will indicate there is not a comment
466  break;
467  }
468  }
469  /*
470  * Is there a comment in the CR for anything that matches the value and
471  * type of the specified attributes.
472  */
473  try {
477  } else {
479  }
480  }
481  } catch (CentralRepoException ex) {
482  logger.log(Level.SEVERE, "Attempted to Query CR for presence of comments in a file node and was unable to perform query, comment column will only reflect caseDB", ex);
483  }
484  return status;
485  }
486 
493  String getTranslatedFileName() {
494  try {
495  return FileNameTranslationUtil.translate(content.getName());
496  } catch (NoServiceProviderException | TranslationException ex) {
497  logger.log(Level.WARNING, MessageFormat.format("Error translating file name (objID={0}))", content.getId()), ex);
498  return "";
499  }
500  }
501 
507  List<ContentTag> getContentTagsFromDatabase() {
508  List<ContentTag> tags = new ArrayList<>();
509  try {
510  tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
511  } catch (TskCoreException | NoCurrentCaseException ex) {
512  logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
513  }
514  return tags;
515  }
516 
517  @Override
518  protected List<Tag> getAllTagsFromDatabase() {
519  return new ArrayList<>(getContentTagsFromDatabase());
520  }
521 
522  static String getContentPath(AbstractFile file) {
523  try {
524  return file.getUniquePath();
525  } catch (TskCoreException ex) {
526  logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + file.getName(), ex); //NON-NLS
527  return ""; //NON-NLS
528  }
529  }
530 
531  static String getContentDisplayName(AbstractFile file) {
532  String name = file.getName();
533  switch (name) {
534  case "..":
535  return DirectoryNode.DOTDOTDIR;
536  case ".":
537  return DirectoryNode.DOTDIR;
538  default:
539  return name;
540  }
541  }
542 
553  static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
554  map.put(NAME.toString(), getContentDisplayName(content));
555  map.put(LOCATION.toString(), getContentPath(content));
556  map.put(MOD_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getMtime()));
557  map.put(CHANGED_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getCtime()));
558  map.put(ACCESS_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getAtime()));
559  map.put(CREATED_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getCrtime()));
560  map.put(SIZE.toString(), content.getSize());
561  map.put(FLAGS_DIR.toString(), content.getDirFlagAsString());
562  map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
563  map.put(KNOWN.toString(), content.getKnown().getName());
564  map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
565  map.put(SHA256HASH.toString(), StringUtils.defaultString(content.getSha256Hash()));
566  map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
567  map.put(EXTENSION.toString(), content.getNameExtension());
568  }
569 }
synchronized void updateSheet(NodeProperty<?>...newProps)
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
static String getFormattedTime(long epochTime)
HasCommentStatus getCommentProperty(List< Tag > tags, List< CorrelationAttributeInstance > attributes)
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
static void fillPropertyMap(Map< String, Object > map, AbstractFile content)
Pair< Long, String > getCountPropertyAndDescription(CorrelationAttributeInstance attributeInstance, String defaultDescription)
static boolean commentExistsOnAttributes(List< CorrelationAttributeInstance > attributes)
static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file)
static void post(String nodeName, Object event)
Long getCountCasesWithOtherInstances(CorrelationAttributeInstance instance)
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:711
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:756

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.