Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
EvalFileObj.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013 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.modules.stix;
20 
27 
28 import java.util.List;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.TimeZone;
32 import java.text.ParseException;
33 import java.text.SimpleDateFormat;
34 import org.mitre.cybox.common_2.ConditionApplicationEnum;
35 
36 import org.mitre.cybox.objects.FileObjectType;
37 import org.mitre.cybox.objects.WindowsExecutableFileObjectType;
38 import org.mitre.cybox.common_2.ConditionTypeEnum;
39 import org.mitre.cybox.common_2.DatatypeEnum;
40 import org.mitre.cybox.common_2.HashType;
41 import org.mitre.cybox.common_2.DateTimeObjectPropertyType;
42 import org.mitre.cybox.common_2.StringObjectPropertyType;
43 import org.mitre.cybox.common_2.UnsignedLongObjectPropertyType;
44 
48 class EvalFileObj extends EvaluatableObject {
49 
50  private final FileObjectType obj;
51 
52  public EvalFileObj(FileObjectType a_obj, String a_id, String a_spacing) {
53  obj = a_obj;
54  id = a_id;
55  spacing = a_spacing;
56  }
57 
58  @Override
59  public synchronized ObservableResult evaluate() {
60 
61  Case case1 = Case.getCurrentCase();
62  SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
63 
64  setWarnings("");
65  String whereClause = "";
66 
67  if (obj.getSizeInBytes() != null) {
68  try {
69  String newClause = processULongObject(obj.getSizeInBytes(), "size"); //NON-NLS
70  whereClause = addClause(whereClause, newClause);
71  } catch (TskCoreException ex) {
72  addWarning(ex.getLocalizedMessage());
73  }
74  }
75 
76  if (obj.getFileName() != null) {
77  try {
78  String newClause = processStringObject(obj.getFileName(), "name"); //NON-NLS
79  whereClause = addClause(whereClause, newClause);
80  } catch (TskCoreException ex) {
81  addWarning(ex.getLocalizedMessage());
82  }
83  }
84 
85  if (obj.getFileExtension() != null) {
86  if ((obj.getFileExtension().getCondition() == null)
87  || (obj.getFileExtension().getCondition() == ConditionTypeEnum.EQUALS)) {
88  String newClause = "name LIKE \'%" + obj.getFileExtension().getValue() + "\'"; //NON-NLS
89  whereClause = addClause(whereClause, newClause);
90  } else {
91  addWarning(
92  "Could not process condition " + obj.getFileExtension().getCondition().value() + " on file extension"); //NON-NLS
93  }
94  }
95 
96  if (obj.getFilePath() != null) {
97  try {
98 
99  String[] parts = obj.getFilePath().getValue().toString().split("##comma##"); //NON-NLS
100  String finalPathStr = "";
101 
102  for (String filePath : parts) {
103  // First, we need to normalize the path
104  String currentFilePath = filePath;
105 
106  // Remove the drive letter
107  if (currentFilePath.matches("^[A-Za-z]:.*")) {
108  currentFilePath = currentFilePath.substring(2);
109  }
110 
111  // Change any backslashes to forward slashes
112  currentFilePath = currentFilePath.replace("\\", "/");
113 
114  // The path needs to start with a slash
115  if (!currentFilePath.startsWith("/")) {
116  currentFilePath = "/" + currentFilePath;
117  }
118 
119  // If the path does not end in a slash, the final part should be the file name.
120  if (!currentFilePath.endsWith("/")) {
121  int lastSlash = currentFilePath.lastIndexOf('/');
122  if (lastSlash >= 0) {
123  currentFilePath = currentFilePath.substring(0, lastSlash + 1);
124  }
125  }
126 
127  // Reconstruct the path string (which may be multi-part)
128  if (!finalPathStr.isEmpty()) {
129  finalPathStr += "##comma##"; //NON-NLS
130  }
131  finalPathStr += currentFilePath;
132  }
133 
134  String newClause = processStringObject(finalPathStr, obj.getFilePath().getCondition(),
135  obj.getFilePath().getApplyCondition(), "parent_path"); //NON-NLS
136 
137  whereClause = addClause(whereClause, newClause);
138  } catch (TskCoreException ex) {
139  addWarning(ex.getLocalizedMessage());
140  }
141  }
142 
143  if (obj.getCreatedTime() != null) {
144  try {
145  String newClause = processTimestampObject(obj.getCreatedTime(), "crtime"); //NON-NLS
146  whereClause = addClause(whereClause, newClause);
147  } catch (TskCoreException ex) {
148  addWarning(ex.getLocalizedMessage());
149  }
150  }
151 
152  if (obj.getModifiedTime() != null) {
153  try {
154  String newClause = processTimestampObject(obj.getModifiedTime(), "mtime"); //NON-NLS
155  whereClause = addClause(whereClause, newClause);
156  } catch (TskCoreException ex) {
157  addWarning(ex.getLocalizedMessage());
158  }
159  }
160 
161  if (obj.getAccessedTime() != null) {
162  try {
163  String newClause = processTimestampObject(obj.getAccessedTime(), "atime"); //NON-NLS
164  whereClause = addClause(whereClause, newClause);
165  } catch (TskCoreException ex) {
166  addWarning(ex.getLocalizedMessage());
167  }
168  }
169 
170  if (obj.getHashes() != null) {
171  for (HashType h : obj.getHashes().getHashes()) {
172  if (h.getSimpleHashValue() != null) {
173  if (h.getType().getValue().equals("MD5")) { //NON-NLS
174  String newClause = "";
175  if(h.getSimpleHashValue().getValue().toString().toLowerCase().contains("##comma##")){
176  String[] parts = h.getSimpleHashValue().getValue().toString().toLowerCase().split("##comma##"); //NON-NLS
177  String hashList = "";
178  for(String s:parts){
179  if(!hashList.isEmpty()){
180  hashList += ", ";
181  }
182  hashList += "\'" + s + "\'";
183  }
184  newClause = "md5 IN (" + hashList + ")";
185  }
186  else{
187  newClause = "md5=\'" + h.getSimpleHashValue().getValue().toString().toLowerCase() + "\'"; //NON-NLS
188  }
189  whereClause = addClause(whereClause, newClause);
190  } else {
191  addWarning("Could not process hash type " + h.getType().getValue().toString()); //NON-NLS
192  }
193  } else {
194  addWarning("Could not process non-simple hash value"); //NON-NLS
195  }
196  }
197  }
198 
199  if (obj instanceof WindowsExecutableFileObjectType) {
200  WindowsExecutableFileObjectType winExe = (WindowsExecutableFileObjectType) obj;
201  if (winExe.getHeaders() != null) {
202  if (winExe.getHeaders().getFileHeader() != null) {
203  if (winExe.getHeaders().getFileHeader().getTimeDateStamp() != null) {
204  try {
205  String result = convertTimestampString(winExe.getHeaders().getFileHeader().getTimeDateStamp().getValue().toString());
206  String newClause = processNumericFields(result,
207  winExe.getHeaders().getFileHeader().getTimeDateStamp().getCondition(),
208  winExe.getHeaders().getFileHeader().getTimeDateStamp().getApplyCondition(),
209  "crtime"); //NON-NLS
210  whereClause = addClause(whereClause, newClause);
211  } catch (TskCoreException ex) {
212  addWarning(ex.getLocalizedMessage());
213  }
214  }
215  }
216  }
217  }
218 
219  String unsupportedFields = listUnsupportedFields();
220  if (!unsupportedFields.isEmpty()) {
221  addWarning("Unsupported fields: " + unsupportedFields); //NON-NLS
222  }
223 
224  if (whereClause.length() > 0) {
225  try {
226  List<AbstractFile> matchingFiles = sleuthkitCase.findAllFilesWhere(whereClause);
227 
228  if (!matchingFiles.isEmpty()) {
229 
230  if (listSecondaryFields().isEmpty()) {
231 
232  List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
233  for (AbstractFile a : matchingFiles) {
234  artData.add(new StixArtifactData(a, id, "FileObject")); //NON-NLS
235  }
236 
237  return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause + getPrintableWarnings(), //NON-NLS
238  spacing, ObservableResult.ObservableState.TRUE, artData);
239  } else {
240 
241  // We need to tag the matching files in Autopsy, so keep track of them
242  List<AbstractFile> secondaryHits = new ArrayList<AbstractFile>();
243 
244  for (AbstractFile file : matchingFiles) {
245  boolean passedTests = true;
246 
247  if (obj.isIsMasqueraded() != null) {
248  List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED);
249  boolean isMasq = false;
250  if (!arts.isEmpty()) {
251  isMasq = true;
252  }
253 
254  if (obj.isIsMasqueraded() != isMasq) {
255  passedTests = false;
256  }
257 
258  }
259 
260  if (obj.getFileFormat() != null) {
261 
262  boolean foundMatch = false;
263  String formatsFound = "";
264  List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO);
265  for (BlackboardArtifact artifact : arts) {
266  for (BlackboardAttribute attr : artifact.getAttributes()) {
267  if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID()) {
268  if (!formatsFound.isEmpty()) {
269  formatsFound += ", ";
270  }
271  formatsFound += attr.getValueString();
272  if (attr.getValueString().equalsIgnoreCase(obj.getFileFormat().getValue().toString())) {
273  foundMatch = true;
274  }
275 
276  // Try again looking for the last part of the Autopsy version as a substring
277  // of the STIX version
278  String type = attr.getValueString().replaceFirst("^.*/", "");
279 
280  // This is reversed of how the comparison normally go
281  if (compareStringObject(type, ConditionTypeEnum.CONTAINS, null, obj.getFileFormat().getValue().toString())) {
282  foundMatch = true;
283  }
284  }
285  }
286  }
287 
288  // It looks like the STIX file formats can be different than what Autopsy stores
289  // (mime vs. unix file), so don't kill a file based on this field not matching.
290  //if (!foundMatch) {
291  // passedTests = false;
292  //}
293  if (formatsFound.isEmpty()) {
294  addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString() //NON-NLS
295  + " (no file formats found)"); //NON-NLS
296  } else {
297  if (!foundMatch) {
298  addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString() //NON-NLS
299  + " against " + formatsFound); //NON-NLS
300  }
301  }
302  }
303 
304  if (passedTests) {
305  secondaryHits.add(file);
306  }
307  }
308 
309  if (secondaryHits.isEmpty()) {
310 
311  return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause //NON-NLS
312  + " but none for secondary tests on " + listSecondaryFields() + getPrintableWarnings(), //NON-NLS
313  spacing, ObservableResult.ObservableState.FALSE, null);
314  } else {
315  List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
316  for (AbstractFile a : secondaryHits) {
317  artData.add(new StixArtifactData(a, id, "FileObject")); //NON-NLS
318  }
319  return new ObservableResult(id, "FileObject: Found " + secondaryHits.size() + " matches for " + whereClause //NON-NLS
320  + " and secondary tests on " + listSecondaryFields() + getPrintableWarnings(), //NON-NLS
321  spacing, ObservableResult.ObservableState.TRUE, artData);
322  }
323  }
324  } else {
325  return new ObservableResult(id, "FileObject: Found no matches for " + whereClause + getPrintableWarnings(), //NON-NLS
326  spacing, ObservableResult.ObservableState.FALSE, null);
327  }
328  } catch (TskCoreException ex) {
329  return new ObservableResult(id, "FileObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS
330  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
331  }
332  } else {
333 
334  }
335 
336  return new ObservableResult(id, "FileObject: No evaluatable fields " + getPrintableWarnings(), spacing, //NON-NLS
337  ObservableResult.ObservableState.INDETERMINATE, null);
338  }
339 
346  private String listSecondaryFields() {
347  String secondaryFields = "";
348 
349  if (obj.isIsMasqueraded() != null) {
350  secondaryFields += "is_masqueraded "; //NON-NLS
351  }
352 
353  if (obj.getFileFormat() != null) {
354  secondaryFields += "File_Format "; //NON-NLS
355  }
356 
357  return secondaryFields;
358  }
359 
365  private String listUnsupportedFields() {
366  String unsupportedFields = "";
367 
368  if (obj.isIsPacked() != null) {
369  unsupportedFields += "is_packed "; //NON-NLS
370  }
371  if (obj.getDevicePath() != null) {
372  unsupportedFields += "Device_Path "; //NON-NLS
373  }
374  if (obj.getFullPath() != null) {
375  unsupportedFields += "Full_Path "; //NON-NLS
376  }
377  if (obj.getMagicNumber() != null) {
378  unsupportedFields += "Magic_Number "; //NON-NLS
379  }
380  if (obj.getDigitalSignatures() != null) {
381  unsupportedFields += "Digital_Signatures "; //NON-NLS
382  }
383  if (obj.getFileAttributesList() != null) {
384  unsupportedFields += "File_Attributes_List "; //NON-NLS
385  }
386  if (obj.getPermissions() != null) {
387  unsupportedFields += "Permissions "; //NON-NLS
388  }
389  if (obj.getUserOwner() != null) {
390  unsupportedFields += "User_Owner "; //NON-NLS
391  }
392  if (obj.getPackerList() != null) {
393  unsupportedFields += "Packer_List "; //NON-NLS
394  }
395  if (obj.getPeakEntropy() != null) {
396  unsupportedFields += "Peak_Entropy "; //NON-NLS
397  }
398  if (obj.getSymLinks() != null) {
399  unsupportedFields += "Sym_Links "; //NON-NLS
400  }
401  if (obj.getByteRuns() != null) {
402  unsupportedFields += "Bytes_Runs "; //NON-NLS
403  }
404  if (obj.getExtractedFeatures() != null) {
405  unsupportedFields += "Extracted_Features "; //NON-NLS
406  }
407  if (obj.getEncryptionAlgorithm() != null) {
408  unsupportedFields += "Encryption_Algorithm "; //NON-NLS
409  }
410  if (obj.getDecryptionKey() != null) {
411  unsupportedFields += "Decryption_Key "; //NON-NLS
412  }
413  if (obj.getCompressionMethod() != null) {
414  unsupportedFields += "Compression_Method "; //NON-NLS
415  }
416  if (obj.getCompressionVersion() != null) {
417  unsupportedFields += "Compression_Version "; //NON-NLS
418  }
419  if (obj.getCompressionComment() != null) {
420  unsupportedFields += "Compression_Comment "; //NON-NLS
421  }
422 
423  return unsupportedFields;
424  }
425 
433  private static long convertTimestamp(String timeStr) throws ParseException {
434  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //NON-NLS
435  dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); //NON-NLS
436  Date parsedDate = dateFormat.parse(timeStr);
437 
438  Long unixTime = parsedDate.getTime() / 1000;
439 
440  return unixTime;
441  }
442 
452  private static String processULongObject(UnsignedLongObjectPropertyType longObj, String fieldName)
453  throws TskCoreException {
454 
455  return processNumericFields(longObj.getValue().toString(), longObj.getCondition(),
456  longObj.getApplyCondition(), fieldName);
457  }
458 
469  private static String processNumericFields(String valueStr, ConditionTypeEnum typeCondition,
470  ConditionApplicationEnum applyCondition, String fieldName)
471  throws TskCoreException {
472 
473  if ((typeCondition == null)
474  || ((typeCondition != ConditionTypeEnum.INCLUSIVE_BETWEEN)
475  && (typeCondition != ConditionTypeEnum.EXCLUSIVE_BETWEEN))) {
476 
477  String fullClause = "";
478 
479  if (valueStr.isEmpty()) {
480  throw new TskCoreException("Empty value field"); //NON-NLS
481  }
482 
483  String[] parts = valueStr.split("##comma##"); //NON-NLS
484 
485  for (String valuePart : parts) {
486  String partialClause;
487 
488  if ((typeCondition == null)
489  || (typeCondition == ConditionTypeEnum.EQUALS)) {
490 
491  partialClause = fieldName + "=" + valuePart;
492  } else if (typeCondition == ConditionTypeEnum.DOES_NOT_EQUAL) {
493  partialClause = fieldName + "!=" + valuePart;
494  } else if (typeCondition == ConditionTypeEnum.GREATER_THAN) {
495  partialClause = fieldName + ">" + valuePart;
496  } else if (typeCondition == ConditionTypeEnum.GREATER_THAN_OR_EQUAL) {
497  partialClause = fieldName + ">=" + valuePart;
498  } else if (typeCondition == ConditionTypeEnum.LESS_THAN) {
499  partialClause = fieldName + "<" + valuePart;
500  } else if (typeCondition == ConditionTypeEnum.LESS_THAN_OR_EQUAL) {
501  partialClause = fieldName + "<=" + valuePart;
502  } else {
503  throw new TskCoreException("Could not process condition " + typeCondition.value() + " on " + fieldName); //NON-NLS
504  }
505 
506  if (fullClause.isEmpty()) {
507 
508  if (parts.length > 1) {
509  fullClause += "( ";
510  }
511  if (applyCondition == ConditionApplicationEnum.NONE) {
512  fullClause += " NOT "; //NON-NLS
513  }
514  fullClause += partialClause;
515  } else {
516  if (applyCondition == ConditionApplicationEnum.ALL) {
517  fullClause += " AND " + partialClause; //NON-NLS
518  } else if (applyCondition == ConditionApplicationEnum.NONE) {
519  fullClause += " AND NOT " + partialClause; //NON-NLS
520  } else {
521  fullClause += " OR " + partialClause; //NON-NLS
522  }
523  }
524  }
525 
526  if (parts.length > 1) {
527  fullClause += " )";
528  }
529 
530  return fullClause;
531  } else {
532  // I don't think apply conditions make sense for these two.
533  if (typeCondition == ConditionTypeEnum.INCLUSIVE_BETWEEN) {
534  String[] parts = valueStr.split("##comma##"); //NON-NLS
535  if (parts.length != 2) {
536  throw new TskCoreException("Unexpected number of arguments in INCLUSIVE_BETWEEN on " + fieldName //NON-NLS
537  + "(" + valueStr + ")");
538  }
539  return (fieldName + ">=" + parts[0] + " AND " + fieldName + "<=" + parts[1]); //NON-NLS
540  } else {
541  String[] parts = valueStr.split("##comma##"); //NON-NLS
542  if (parts.length != 2) {
543  throw new TskCoreException("Unexpected number of arguments in EXCLUSIVE_BETWEEN on " + fieldName //NON-NLS
544  + "(" + valueStr + ")");
545  }
546  return (fieldName + ">" + parts[0] + " AND " + fieldName + "<" + parts[1]); //NON-NLS
547  }
548  }
549  }
550 
559  private static String processStringObject(StringObjectPropertyType stringObj, String fieldName)
560  throws TskCoreException {
561 
562  return processStringObject(stringObj.getValue().toString(), stringObj.getCondition(),
563  stringObj.getApplyCondition(), fieldName);
564  }
565 
576  public static String processStringObject(String valueStr, ConditionTypeEnum condition,
577  ConditionApplicationEnum applyCondition, String fieldName)
578  throws TskCoreException {
579 
580  String fullClause = "";
581  String lowerFieldName = "lower(" + fieldName + ")"; //NON-NLS
582 
583  if (valueStr.isEmpty()) {
584  throw new TskCoreException("Empty value field"); //NON-NLS
585  }
586 
587  String[] parts = valueStr.split("##comma##"); //NON-NLS
588 
589  for (String value : parts) {
590  String lowerValue = value.toLowerCase();
591  String partialClause;
592  if ((condition == null)
593  || (condition == ConditionTypeEnum.EQUALS)) {
594  partialClause = lowerFieldName + "=\'" + lowerValue + "\'";
595  } else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) {
596  partialClause = lowerFieldName + " !=\'%" + lowerValue + "%\'";
597  } else if (condition == ConditionTypeEnum.CONTAINS) {
598  partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "%\'"; //NON-NLS
599  } else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) {
600  partialClause = lowerFieldName + " NOT LIKE \'%" + lowerValue + "%\'"; //NON-NLS
601  } else if (condition == ConditionTypeEnum.STARTS_WITH) {
602  partialClause = lowerFieldName + " LIKE \'" + lowerValue + "%\'"; //NON-NLS
603  } else if (condition == ConditionTypeEnum.ENDS_WITH) {
604  partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "\'"; //NON-NLS
605  } else {
606  throw new TskCoreException("Could not process condition " + condition.value() + " on " + fieldName); //NON-NLS
607  }
608 
609  if (fullClause.isEmpty()) {
610 
611  if (parts.length > 1) {
612  fullClause += "( ";
613  }
614  if (applyCondition == ConditionApplicationEnum.NONE) {
615  fullClause += " NOT "; //NON-NLS
616  }
617  fullClause += partialClause;
618  } else {
619  if (applyCondition == ConditionApplicationEnum.ALL) {
620  fullClause += " AND " + partialClause; //NON-NLS
621  } else if (applyCondition == ConditionApplicationEnum.NONE) {
622  fullClause += " AND NOT " + partialClause; //NON-NLS
623  } else {
624  fullClause += " OR " + partialClause; //NON-NLS
625  }
626  }
627  }
628 
629  if (parts.length > 1) {
630  fullClause += " )";
631  }
632 
633  return fullClause;
634  }
635 
645  private static String processTimestampObject(DateTimeObjectPropertyType dateObj, String fieldName)
646  throws TskCoreException {
647 
648  if (DatatypeEnum.DATE_TIME == dateObj.getDatatype()) {
649 
650  // Change the string into unix timestamps
651  String result = convertTimestampString(dateObj.getValue().toString());
652  return processNumericFields(result, dateObj.getCondition(), dateObj.getApplyCondition(), fieldName);
653 
654  } else {
655  throw new TskCoreException("Found non DATE_TIME field on " + fieldName); //NON-NLS
656  }
657  }
658 
667  private static String convertTimestampString(String timestampStr)
668  throws TskCoreException {
669  try {
670  String result = "";
671  if (timestampStr.length() > 0) {
672  String[] parts = timestampStr.split("##comma##"); //NON-NLS
673 
674  for (int i = 0; i < parts.length - 1; i++) {
675  long unixTime = convertTimestamp(parts[i]);
676  result += unixTime + "##comma##"; //NON-NLS
677  }
678  result += convertTimestamp(parts[parts.length - 1]);
679  }
680  return result;
681  } catch (java.text.ParseException ex) {
682  throw new TskCoreException("Error parsing timestamp string " + timestampStr); //NON-NLS
683  }
684 
685  }
686 
694  private static String addClause(String a_clause, String a_newClause) {
695 
696  if ((a_clause == null) || a_clause.isEmpty()) {
697  return a_newClause;
698  }
699 
700  return (a_clause + " AND " + a_newClause); //NON-NLS
701  }
702 
703 }

Copyright © 2012-2015 Basis Technology. Generated on: Mon Oct 19 2015
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.