001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019: package org.netbeans.modules.wsdlextensions.file.validator;
020:
021: import java.text.MessageFormat;
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.ResourceBundle;
028: import java.util.regex.Matcher;
029: import java.util.regex.Pattern;
030:
031: import org.netbeans.modules.xml.wsdl.model.Binding;
032: import org.netbeans.modules.xml.wsdl.model.BindingInput;
033: import org.netbeans.modules.xml.wsdl.model.BindingOperation;
034: import org.netbeans.modules.xml.wsdl.model.BindingOutput;
035: import org.netbeans.modules.xml.wsdl.model.Definitions;
036: import org.netbeans.modules.xml.wsdl.model.Input;
037: import org.netbeans.modules.xml.wsdl.model.OperationParameter;
038: import org.netbeans.modules.xml.wsdl.model.Part;
039: import org.netbeans.modules.xml.wsdl.model.Port;
040: import org.netbeans.modules.xml.wsdl.model.Service;
041: import org.netbeans.modules.xml.wsdl.model.WSDLModel;
042: import org.netbeans.modules.xml.xam.Model;
043: import org.netbeans.modules.xml.xam.Model.State;
044: import org.netbeans.modules.xml.xam.dom.NamedComponentReference;
045: import org.netbeans.modules.xml.wsdl.model.Message;
046: import org.netbeans.modules.xml.xam.spi.Validation;
047: import org.netbeans.modules.xml.xam.spi.Validation.ValidationType;
048: import org.netbeans.modules.xml.xam.spi.ValidationResult;
049: import org.netbeans.modules.xml.xam.spi.Validator;
050: import org.netbeans.modules.xml.xam.spi.Validator.ResultItem;
051:
052: import org.netbeans.modules.wsdlextensions.file.model.FileAddress;
053: import org.netbeans.modules.wsdlextensions.file.model.FileBinding;
054: import org.netbeans.modules.wsdlextensions.file.model.FileComponent;
055: import org.netbeans.modules.wsdlextensions.file.model.FileMessage;
056: import org.netbeans.modules.wsdlextensions.file.model.FileOperation;
057:
058: /**
059: * This class enables semantic validations for
060: * File WSDL documents.
061: *
062: * @author sweng
063: */
064: public class FileComponentValidator implements Validator,
065: FileComponent.Visitor {
066:
067: public static final String NUMBER_MARKER = "%d"; // transient sequence
068: public static final String TIMESTAMP_MARKER = "%t";
069: public static final String GUID_MARKER = "%u";
070: public static final String SEQ_REF_EXP = "%\\{([0-9_\\-\\.a-zA-Z]+)\\}"; // named sequence
071: public static final String UUID_REGEX = "[0-9[abcdef]]{8}-[0-9[abcdef]]{4}-[0-9[abcdef]]{4}-[0-9[abcdef]]{4}-[0-9[abcdef]]{12}";
072: public static final Pattern SEQ_REF_EXP_PATT = Pattern
073: .compile(SEQ_REF_EXP);
074:
075: /** Utility to get the regular expression to match
076: * the date format.
077: * Reason this is in its own method is that we can
078: * localize the date format recognition pattern in
079: * this method only.
080: * The pattern we currently support is UTC
081: */
082: public static final String TIMESTAMP_REGEX = "[0-9]{4,4}(0[1-9]|1[0-2])([0-2][0-9]|3[0-1])(\\-(1[0-9]|2[0-3])\\-[0-5][0-9]\\-[0-5][0-9]\\-[0-9]{0,3})?";
083:
084: private static final ResourceBundle mMessages = ResourceBundle
085: .getBundle("org.netbeans.modules.wsdlextensions.file.validator.Bundle");
086:
087: private Validation mValidation;
088: private ValidationType mValidationType;
089: private ValidationResult mValidationResult;
090:
091: public static final ValidationResult EMPTY_RESULT = new ValidationResult(
092: Collections.EMPTY_SET, Collections.EMPTY_SET);
093:
094: public FileComponentValidator() {
095: }
096:
097: /**
098: * Returns name of this validation service.
099: */
100: public String getName() {
101: return getClass().getName();
102: }
103:
104: /**
105: * Validates given model.
106: *
107: * @param model model to validate.
108: * @param validation reference to the validation context.
109: * @param validationType the type of validation to perform
110: * @return ValidationResult.
111: */
112: public ValidationResult validate(Model model,
113: Validation validation, ValidationType validationType) {
114: mValidation = validation;
115: mValidationType = validationType;
116:
117: HashSet<ResultItem> results = new HashSet<ResultItem>();
118: HashSet<Model> models = new HashSet<Model>();
119: models.add(model);
120: mValidationResult = new ValidationResult(results, models);
121:
122: // Traverse the model
123: if (model instanceof WSDLModel) {
124: WSDLModel wsdlModel = (WSDLModel) model;
125:
126: if (model.getState() == State.NOT_WELL_FORMED) {
127: return EMPTY_RESULT;
128: }
129:
130: Definitions defs = wsdlModel.getDefinitions();
131: Iterator<Binding> bindings = defs.getBindings().iterator();
132:
133: while (bindings.hasNext()) {
134: Binding binding = bindings.next();
135:
136: if (binding.getType() == null
137: || binding.getType().get() == null) {
138: continue;
139: }
140:
141: int numFileBindings = binding.getExtensibilityElements(
142: FileBinding.class).size();
143:
144: if (numFileBindings == 0) {
145: continue;
146: }
147:
148: if (numFileBindings > 0 && numFileBindings != 1) {
149: results
150: .add(new Validator.ResultItem(
151: this ,
152: Validator.ResultType.ERROR,
153: binding,
154: mMessages
155: .getString("FileBindingValidation.only_one_binding_allowed")));
156: }
157:
158: Iterator<BindingOperation> bindingOps = binding
159: .getBindingOperations().iterator();
160: boolean foundFileOp = false;
161: int count = 0;
162: while (bindingOps.hasNext()) {
163: BindingOperation bindingOp = bindingOps.next();
164: List fileOpsList = bindingOp
165: .getExtensibilityElements(FileOperation.class);
166: Iterator<FileOperation> fileOps = fileOpsList
167: .iterator();
168:
169: while (fileOps.hasNext()) {
170: fileOps.next().accept(this );
171: }
172:
173: if (fileOpsList.size() > 0) {
174: foundFileOp = true;
175: BindingInput bindingInput = bindingOp
176: .getBindingInput();
177: if (bindingInput != null) {
178: count = 0;
179: Iterator<FileMessage> fileMessages = bindingInput
180: .getExtensibilityElements(
181: FileMessage.class)
182: .iterator();
183: if (fileMessages != null) {
184: while (fileMessages.hasNext()) {
185: count++;
186: FileMessage fileMessage = fileMessages
187: .next();
188: fileMessage.accept(this );
189: additionalFileMessageValidation(
190: bindingOp, bindingInput
191: .getInput().get(),
192: fileMessage);
193: }
194: if (count > 1) {
195: results
196: .add(new Validator.ResultItem(
197: this ,
198: Validator.ResultType.ERROR,
199: bindingOp,
200: getMessage(
201: "FileInputValidation.only_one_message_allowed",
202: bindingOp
203: .getName())
204: + count));
205: }
206: }
207: }
208:
209: BindingOutput bindingOutput = bindingOp
210: .getBindingOutput();
211: if (bindingOutput != null) {
212: // reset count for output
213: count = 0;
214: Iterator<FileMessage> fileMessages = bindingOutput
215: .getExtensibilityElements(
216: FileMessage.class)
217: .iterator();
218: if (fileMessages != null) {
219: while (fileMessages.hasNext()) {
220: count++;
221: FileMessage fileMessage = fileMessages
222: .next();
223: fileMessage.accept(this );
224: additionalFileMessageValidation(
225: bindingOp, bindingOutput
226: .getOutput().get(),
227: fileMessage);
228: }
229: if (count > 1) {
230: results
231: .add(new Validator.ResultItem(
232: this ,
233: Validator.ResultType.ERROR,
234: bindingOp,
235: getMessage(
236: "FileOutputValidation.only_one_message_allowed",
237: bindingOp
238: .getName())
239: + count));
240: }
241: }
242: }
243: }
244: }
245: // validating: file:binding found but no file:operation is defined
246: if (numFileBindings > 0 && !foundFileOp) {
247: results
248: .add(new Validator.ResultItem(
249: this ,
250: Validator.ResultType.ERROR,
251: binding,
252: getMessage(
253: "FileOperationValidation.no_operation_defined",
254: binding.getName())));
255: }
256: // validating: found file:operation but no file:binding is defined
257: if (numFileBindings == 0 && foundFileOp) {
258: results
259: .add(new Validator.ResultItem(
260: this ,
261: Validator.ResultType.ERROR,
262: binding,
263: getMessage(
264: "FileBindingValidation.no_binding_defined",
265: binding.getName())));
266: }
267: }
268:
269: Iterator<Service> services = defs.getServices().iterator();
270: while (services.hasNext()) {
271: Iterator<Port> ports = services.next().getPorts()
272: .iterator();
273: while (ports.hasNext()) {
274: Port port = ports.next();
275: if (port.getBinding() != null) {
276: Binding binding = port.getBinding().get();
277: if (binding != null) {
278: int numRelatedFileBindings = binding
279: .getExtensibilityElements(
280: FileBinding.class).size();
281: Iterator<FileAddress> fileAddresses = port
282: .getExtensibilityElements(
283: FileAddress.class)
284: .iterator();
285: if ((numRelatedFileBindings > 0)
286: && (!fileAddresses.hasNext())) {
287: results
288: .add(new Validator.ResultItem(
289: this ,
290: Validator.ResultType.ERROR,
291: port,
292: getMessage(
293: "FileAddressValidation.no_file_address",
294: port.getName())));
295: }
296:
297: if (port.getExtensibilityElements(
298: FileAddress.class).size() > 1) {
299: results
300: .add(new Validator.ResultItem(
301: this ,
302: Validator.ResultType.ERROR,
303: port,
304: getMessage(
305: "FileAddressValidation.only_one_address_allowed",
306: port.getName())));
307: }
308: while (fileAddresses.hasNext()) {
309: fileAddresses.next().accept(this );
310: }
311: }
312: }
313: }
314: }
315: }
316: // Clear out our state
317: mValidation = null;
318: mValidationType = null;
319:
320: return mValidationResult;
321: }
322:
323: public void visit(FileAddress target) {
324: Collection<ResultItem> results = mValidationResult
325: .getValidationResult();
326: if (target.getRelativePath()) {
327: if (target.getPathRelativeTo() == null) {
328: results
329: .add(new Validator.ResultItem(
330: this ,
331: Validator.ResultType.ERROR,
332: target,
333: getMessage("FileAddressValidation.missing_root_path")));
334: } else if (!target.getPathRelativeTo().equals("User Home")
335: && !target.getPathRelativeTo().equals(
336: "Current Working Dir")
337: && !target.getPathRelativeTo().equals(
338: "Default System Temp Dir")) {
339: try {
340: if (!Utils.hasMigrationEnvVarRef(target
341: .getPathRelativeTo())) {
342: results
343: .add(new Validator.ResultItem(
344: this ,
345: Validator.ResultType.ERROR,
346: target,
347: getMessage(
348: "FileAddressValidation.invalid_root_path",
349: target
350: .getPathRelativeTo())));
351: }
352: } catch (Exception ex) {
353: results
354: .add(new Validator.ResultItem(
355: this ,
356: Validator.ResultType.ERROR,
357: target,
358: getMessage(
359: "FileAddressValidation.Exception_when_searching_varref_in_root_path",
360: new Object[] {
361: target
362: .getPathRelativeTo(),
363: ex })));
364: }
365: }
366: } else {
367: if (target.getFileDirectory() == null
368: || target.getFileDirectory().trim().equals("")) {
369: results
370: .add(new Validator.ResultItem(
371: this ,
372: Validator.ResultType.ERROR,
373: target,
374: getMessage("FileAddressValidation.missing_file_directory")));
375: }
376: }
377: }
378:
379: public void visit(FileBinding target) {
380: // no attributes defined
381: }
382:
383: public void visit(FileOperation target) {
384: // no attributes defined
385: }
386:
387: public void visit(FileMessage target) {
388: Collection<ResultItem> results = mValidationResult
389: .getValidationResult();
390: // validating: fileName is a required attribute in file:message
391: if (target.getFileName() == null
392: || target.getFileName().trim().equals("")) {
393: results
394: .add(new Validator.ResultItem(
395: this ,
396: Validator.ResultType.ERROR,
397: target,
398: getMessage("FileMessageValidation.missing_file_name")));
399: }
400:
401: // validating: if use="encoded", encodingStyle must be specified
402: if (target.getFileUseType() != null
403: && target.getFileUseType().equals("encoded")) {
404: if (target.getFileEncodingStyle() == null
405: || target.getFileEncodingStyle().trim().equals("")) {
406: results
407: .add(new Validator.ResultItem(
408: this ,
409: Validator.ResultType.ERROR,
410: target,
411: getMessage("FileMessageValidation.no_encoding_style")));
412: }
413: }
414:
415: // validating: if fileNameIsPattern="true", fileName must be a valid pattern
416: if (target.getFileNameIsPattern()) {
417: boolean hasVarRef = false;
418: try {
419: hasVarRef = Utils.hasMigrationEnvVarRef(target
420: .getFileName());
421: } catch (Exception ex) {
422: results
423: .add(new Validator.ResultItem(
424: this ,
425: Validator.ResultType.ERROR,
426: target,
427: getMessage(
428: "FileMessageValidation.Exception_searching_varref_in_file_name_pattern",
429: new Object[] {
430: target.getFileName(),
431: ex })));
432: }
433: String[] decomposed = new String[3];
434: FileNamePatternType type = validatePattern(target
435: .getFileName(), decomposed, results, target);
436: if (type == FileNamePatternType.NAME_INVALID && !hasVarRef) {
437: results
438: .add(new Validator.ResultItem(
439: this ,
440: Validator.ResultType.ERROR,
441: target,
442: getMessage(
443: "FileMessageValidation.invalid_file_name_pattern",
444: target.getFileName())));
445: }
446: }
447:
448: // validating: fileType is "text"
449: if (target.getFileType() != null) {
450: String fileType = target.getFileType();
451: boolean hasVarRef = false;
452: try {
453: hasVarRef = Utils.hasMigrationEnvVarRef(fileType);
454: } catch (Exception ex) {
455: results
456: .add(new Validator.ResultItem(
457: this ,
458: Validator.ResultType.ERROR,
459: target,
460: getMessage(
461: "FileMessageValidation.Exception_when_searching_varref_in_file_type",
462: new Object[] {
463: target.getFileType(),
464: ex })));
465: }
466: if (!target.getFileType().equals("text")
467: && !target.getFileType().equals("binary")
468: && !hasVarRef) {
469: results
470: .add(new Validator.ResultItem(
471: this ,
472: Validator.ResultType.ERROR,
473: target,
474: getMessage(
475: "FileMessageValidation.invalid_file_type",
476: target.getFileType())));
477: }
478: if (target.getFileType().equals("binary")) {
479: results
480: .add(new Validator.ResultItem(
481: this ,
482: Validator.ResultType.WARNING,
483: target,
484: getMessage(
485: "FileMessageValidation.unsupported_file_type",
486: target.getFileType())));
487: }
488: }
489:
490: // validating: handling multiple records
491: if (target.getMultipleRecordsPerFile()) {
492: if (target.getRecordDelimiter() == null) {
493: results
494: .add(new Validator.ResultItem(
495: this ,
496: Validator.ResultType.WARNING,
497: target,
498: getMessage("FileMessageValidation.no_delimiter_defined")));
499: }
500: }
501:
502: // validating: maxBytesPerRecord
503: if (target.getMaxBytesPerRecord() != null
504: && target.getMaxBytesPerRecord().longValue() < 0) {
505: results.add(new Validator.ResultItem(this ,
506: Validator.ResultType.ERROR, target, getMessage(
507: "FileMessageValidation.invalid_max_bytes",
508: "" + target.getMaxBytesPerRecord())));
509: }
510:
511: // validating: pollingInterval
512: if (target.getPollingInterval() != null
513: && target.getPollingInterval().longValue() < 0) {
514: results
515: .add(new Validator.ResultItem(
516: this ,
517: Validator.ResultType.ERROR,
518: target,
519: getMessage(
520: "FileMessageValidation.invalid_polling_interval",
521: "" + target.getPollingInterval())));
522: }
523: }
524:
525: private void additionalFileMessageValidation(
526: BindingOperation bindingOp, OperationParameter param,
527: FileMessage target) {
528: Collection<ResultItem> results = mValidationResult
529: .getValidationResult();
530:
531: String part = target.getPart();
532: if (part != null) {
533: // make sure textPart references a vald wsdl message part
534: if (!matchesValidMessagePart(param.getMessage(), part)) {
535: results
536: .add(new Validator.ResultItem(
537: this ,
538: Validator.ResultType.ERROR,
539: target,
540: getMessage(
541: "FileMessageValidation.part_name_not_matching",
542: new Object[] {
543: bindingOp.getName(),
544: (param instanceof Input) ? "input"
545: : "output",
546: part,
547: param.getMessage()
548: .getQName() })));
549: }
550: }
551: }
552:
553: private String getMessage(String key) {
554: return mMessages.getString(key);
555: }
556:
557: private String getMessage(String key, String param) {
558: return getMessage(key, new Object[] { param });
559: }
560:
561: private String getMessage(String key, Object[] params) {
562: String fmt = mMessages.getString(key);
563: if (params != null) {
564: return MessageFormat.format(fmt, params);
565: } else {
566: return fmt;
567: }
568: }
569:
570: private boolean matchesValidMessagePart(
571: NamedComponentReference<Message> wsdlMessage,
572: String partName) {
573: boolean isValid = false;
574: Iterator<Part> partIter = wsdlMessage.get().getParts()
575: .iterator();
576: while (partIter.hasNext()) {
577: Part p = partIter.next();
578: if (p.getName().equals(partName)) {
579: isValid = true;
580: break;
581: }
582: }
583: return isValid;
584: }
585:
586: private FileNamePatternType validatePattern(String pattern,
587: String[] decomposed, Collection<ResultItem> results,
588: FileMessage target) {
589: // decomposed array must be a 3 element string array
590: // with:
591: // decomposed[0] as string before the pattern
592: // decomposed[1] as the pattern it self
593: // decomposed[2] as string after the pattern
594: if (pattern == null || pattern.trim().length() == 0) {
595: results
596: .add(new Validator.ResultItem(
597: this ,
598: Validator.ResultType.ERROR,
599: target,
600: getMessage("FileMessageValidation.Invalid_pattern_blank")));
601: }
602:
603: FileNamePatternType type = FileNamePatternType.NAME_INVALID;
604: int index1 = 0;
605: int index2 = 0;
606: int pcnt = 0;
607: String prefix = "";
608: String suffix = "";
609: String patternFound = null;
610:
611: if ((index1 = pattern.indexOf(NUMBER_MARKER)) >= 0) {
612: // has at least 1 %d
613: index2 = pattern.lastIndexOf(NUMBER_MARKER);
614: if (index1 < index2) {
615: // more than one occurrence
616: results
617: .add(new Validator.ResultItem(
618: this ,
619: Validator.ResultType.ERROR,
620: target,
621: getMessage(
622: "FileMessageValidation.Invalid_pattern_only_1_seq_allowed",
623: pattern)));
624: }
625: pcnt++;
626: type = FileNamePatternType.NAME_WITH_SEQ;
627: patternFound = NUMBER_MARKER;
628: prefix = index1 == 0 ? "" : pattern.substring(0, index1);
629: suffix = index1 + 2 >= pattern.length() ? "" : pattern
630: .substring(index1 + 2);
631: }
632:
633: if ((index1 = pattern.indexOf(GUID_MARKER)) >= 0) {
634: // has at least 1 %u
635: index2 = pattern.lastIndexOf(GUID_MARKER);
636: if (index1 < index2) {
637: // more than one occurrence
638: results
639: .add(new Validator.ResultItem(
640: this ,
641: Validator.ResultType.ERROR,
642: target,
643: getMessage(
644: "FileMessageValidation.Invalid_pattern_only_1_uuid_allowed",
645: pattern)));
646: }
647: if (pcnt > 0) {
648: // more than one pattern mark
649: results
650: .add(new Validator.ResultItem(
651: this ,
652: Validator.ResultType.ERROR,
653: target,
654: getMessage(
655: "FileMessageValidation.Invalid_pattern_only_1_pattern_allowed",
656: pattern)));
657: }
658: pcnt++;
659: type = FileNamePatternType.NAME_WITH_UUID;
660: patternFound = GUID_MARKER;
661: prefix = index1 == 0 ? "" : pattern.substring(0, index1);
662: suffix = index1 + 2 >= pattern.length() ? "" : pattern
663: .substring(index1 + 2);
664: }
665:
666: if ((index1 = pattern.indexOf(TIMESTAMP_MARKER)) >= 0) {
667: // has at least 1 %t
668: index2 = pattern.lastIndexOf(TIMESTAMP_MARKER);
669: if (index1 < index2) {
670: // more than one occurrence
671: results
672: .add(new Validator.ResultItem(
673: this ,
674: Validator.ResultType.ERROR,
675: target,
676: getMessage(
677: "FileMessageValidation.Invalid_pattern_only_1_timestamp_allowed",
678: pattern)));
679: }
680: if (pcnt > 0) {
681: // more than one pattern mark
682: results
683: .add(new Validator.ResultItem(
684: this ,
685: Validator.ResultType.ERROR,
686: target,
687: getMessage(
688: "FileMessageValidation.Invalid_pattern_only_1_pattern_allowed",
689: pattern)));
690: }
691: pcnt++;
692: type = FileNamePatternType.NAME_WITH_TIMESTAMP;
693: patternFound = TIMESTAMP_MARKER;
694: prefix = index1 == 0 ? "" : pattern.substring(0, index1);
695: suffix = index1 + 2 >= pattern.length() ? "" : pattern
696: .substring(index1 + 2);
697: }
698:
699: // added for named sequence (persisted sequence)
700: if ((index1 = pattern.indexOf("%{")) >= 0) {
701: Matcher m = SEQ_REF_EXP_PATT.matcher(pattern);
702: if (m.find()) {
703: patternFound = m.group(1);
704: String wholeMatch = m.group();
705: if (pcnt > 0 || m.find()) {
706: // more than one pattern detected
707: results
708: .add(new Validator.ResultItem(
709: this ,
710: Validator.ResultType.ERROR,
711: target,
712: getMessage(
713: "FileMessageValidation.Invalid_pattern_only_1_pattern_allowed",
714: pattern)));
715: }
716: index1 = pattern.indexOf(wholeMatch);
717: pcnt++;
718: type = FileNamePatternType.NAME_WITH_PERSISTED_SEQ;
719: prefix = index1 == 0 ? "" : pattern
720: .substring(0, index1);
721: suffix = index1 + wholeMatch.length() >= pattern
722: .length() ? "" : pattern.substring(index1
723: + wholeMatch.length());
724: }
725: }
726:
727: if (pcnt == 0) {
728: // note, if there is env var reference, then it is too early to say
729: // the file name does not have marker
730: // postpone to runtime
731: boolean hasVarRef = false;
732: try {
733: hasVarRef = Utils.hasMigrationEnvVarRef(pattern);
734: } catch (Exception ex) {
735: results
736: .add(new Validator.ResultItem(
737: this ,
738: Validator.ResultType.ERROR,
739: target,
740: getMessage(
741: "FileMessageValidation.Exception_when_searching_varref_in_pattern",
742: new Object[] { pattern, ex })));
743: }
744: if (!hasVarRef) {
745: results
746: .add(new Validator.ResultItem(
747: this ,
748: Validator.ResultType.ERROR,
749: target,
750: getMessage(
751: "FileMessageValidation.Invalid_pattern_no_marker_found",
752: pattern)));
753: }
754: }
755:
756: decomposed[0] = prefix;
757: decomposed[1] = patternFound;
758: decomposed[2] = suffix;
759:
760: return type;
761: }
762: }
|