001: package org.drools.rule;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.ByteArrayOutputStream;
021: import java.io.Externalizable;
022: import java.io.IOException;
023: import java.io.ObjectInput;
024: import java.io.ObjectOutput;
025: import java.io.ObjectOutputStream;
026: import java.util.ArrayList;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.LinkedHashMap;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035:
036: import org.drools.common.DroolsObjectInputStream;
037: import org.drools.facttemplates.FactTemplate;
038: import org.drools.ruleflow.common.core.Process;
039: import org.drools.util.StringUtils;
040:
041: /**
042: * Collection of related <code>Rule</code>s.
043: *
044: * @see Rule
045: *
046: * @author <a href="mail:bob@werken.com">bob mcwhirter </a>
047: *
048: * @version $Id: Package.java,v 1.1 2005/07/26 01:06:31 mproctor Exp $
049: */
050: public class Package implements Externalizable {
051: // ------------------------------------------------------------
052: // Constants`
053: // ------------------------------------------------------------
054:
055: /**
056: *
057: */
058: private static final long serialVersionUID = 400L;
059:
060: // ------------------------------------------------------------
061: // Instance members
062: // ------------------------------------------------------------
063:
064: /** Name of the pkg. */
065: private String name;
066:
067: /** Set of all rule-names in this <code>Package</code>. */
068: private Map rules;
069:
070: private Set imports;
071:
072: private List functions;
073:
074: private Set staticImports;
075:
076: private Map globals;
077:
078: private Map factTemplates;
079:
080: private Map ruleFlows;
081:
082: private PackageCompilationData packageCompilationData;
083:
084: /** This is to indicate the the package has no errors during the compilation/building phase */
085: private boolean valid = true;
086:
087: /** This will keep a summary error message as to why this package is not valid */
088: private String errorSummary;
089:
090: // ------------------------------------------------------------
091: // Constructors
092: // ------------------------------------------------------------
093:
094: /**
095: * Default constructor - for Externalizable. This should never be used by a user, as it
096: * will result in an invalid state for the instance.
097: */
098: public Package() {
099:
100: }
101:
102: /**
103: * Construct.
104: *
105: * @param name
106: * The name of this <code>Package</code>.
107: */
108: public Package(final String name) {
109: this (name, null);
110: }
111:
112: /**
113: * Construct.
114: *
115: * @param name
116: * The name of this <code>Package</code>.
117: */
118: public Package(final String name, ClassLoader parentClassLoader) {
119: this .name = name;
120: this .imports = new HashSet(2);
121: this .staticImports = Collections.EMPTY_SET;
122: this .rules = new LinkedHashMap();
123: this .ruleFlows = Collections.EMPTY_MAP;
124: this .globals = Collections.EMPTY_MAP;
125: this .factTemplates = Collections.EMPTY_MAP;
126: this .functions = Collections.EMPTY_LIST;
127:
128: // This classloader test should only be here for unit testing, too much legacy api to want to change by hand at the moment
129: if (parentClassLoader == null) {
130: parentClassLoader = Thread.currentThread()
131: .getContextClassLoader();
132: if (parentClassLoader == null) {
133: parentClassLoader = getClass().getClassLoader();
134: }
135: }
136: this .packageCompilationData = new PackageCompilationData(
137: parentClassLoader);
138: }
139:
140: /**
141: * Handles the write serialization of the Package. Patterns in Rules may reference generated data which cannot be serialized by default methods.
142: * The Package uses PackageCompilationData to hold a reference to the generated bytecode. The generated bytecode must be restored before any Rules.
143: *
144: */
145: public void writeExternal(final ObjectOutput stream)
146: throws IOException {
147: stream.writeObject(this .packageCompilationData);
148: stream.writeObject(this .name);
149: stream.writeObject(this .imports);
150: stream.writeObject(this .staticImports);
151: stream.writeObject(this .globals);
152: stream.writeObject(this .ruleFlows);
153: stream.writeBoolean(this .valid);
154:
155: // Rules must be restored by an ObjectInputStream that can resolve using a given ClassLoader to handle seaprately by storing as
156: // a byte[]
157: final ByteArrayOutputStream bos = new ByteArrayOutputStream();
158: final ObjectOutput out = new ObjectOutputStream(bos);
159: out.writeObject(this .rules);
160: stream.writeObject(bos.toByteArray());
161: }
162:
163: /**
164: * Handles the read serialization of the Package. Patterns in Rules may reference generated data which cannot be serialized by default methods.
165: * The Package uses PackageCompilationData to hold a reference to the generated bytecode; which must be restored before any Rules.
166: * A custom ObjectInputStream, able to resolve classes against the bytecode in the PackageCompilationData, is used to restore the Rules.
167: *
168: */
169: public void readExternal(final ObjectInput stream)
170: throws IOException, ClassNotFoundException {
171: // PackageCompilationData must be restored before Rules as it has the ClassLoader needed to resolve the generated code references in Rules
172: this .packageCompilationData = (PackageCompilationData) stream
173: .readObject();
174: this .name = (String) stream.readObject();
175: this .imports = (Set) stream.readObject();
176: this .staticImports = (Set) stream.readObject();
177: this .globals = (Map) stream.readObject();
178: this .ruleFlows = (Map) stream.readObject();
179: this .valid = stream.readBoolean();
180:
181: // Return the rules stored as a byte[]
182: final byte[] bytes = (byte[]) stream.readObject();
183:
184: // Use a custom ObjectInputStream that can resolve against a given classLoader
185: final DroolsObjectInputStream streamWithLoader = new DroolsObjectInputStream(
186: new ByteArrayInputStream(bytes),
187: this .packageCompilationData.getClassLoader());
188:
189: this .rules = (Map) streamWithLoader.readObject();
190: }
191:
192: // ------------------------------------------------------------
193: // Instance methods
194: // ------------------------------------------------------------
195:
196: /**
197: * Retrieve the name of this <code>Package</code>.
198: *
199: * @return The name of this <code>Package</code>.
200: */
201: public String getName() {
202: return this .name;
203: }
204:
205: public void addImport(final String importEntry) {
206: this .imports.add(importEntry);
207: }
208:
209: public void removeImport(final String importEntry) {
210: this .imports.remove(importEntry);
211: }
212:
213: public Set getImports() {
214: return this .imports;
215: }
216:
217: public void addStaticImport(final String functionImport) {
218: if (this .staticImports == Collections.EMPTY_SET) {
219: this .staticImports = new HashSet(2);
220: }
221: this .staticImports.add(functionImport);
222: }
223:
224: public void addFunction(final String functionName) {
225: if (this .functions == Collections.EMPTY_LIST) {
226: this .functions = new ArrayList(1);
227: }
228:
229: this .functions.add(functionName);
230: }
231:
232: public List getFunctions() {
233: return this .functions;
234: }
235:
236: public void removeFunctionImport(final String functionImport) {
237: this .staticImports.remove(functionImport);
238: }
239:
240: public Set getStaticImports() {
241: return this .staticImports;
242: }
243:
244: public void addGlobal(final String identifier, final Class clazz) {
245: if (this .globals == Collections.EMPTY_MAP) {
246: this .globals = new HashMap(1);
247: }
248: this .globals.put(identifier, clazz);
249: }
250:
251: public void removeGlobal(final String identifier) {
252: this .globals.remove(identifier);
253: }
254:
255: public Map getGlobals() {
256: return this .globals;
257: }
258:
259: public PackageCompilationData removeFunction(
260: final String functionName) {
261: if (!this .functions.remove(functionName)) {
262: return null;
263: }
264: this .packageCompilationData.remove(this .name + "."
265: + StringUtils.ucFirst(functionName));
266: return this .packageCompilationData;
267: }
268:
269: public FactTemplate getFactTemplate(final String name) {
270: return (FactTemplate) this .factTemplates.get(name);
271: }
272:
273: public void addFactTemplate(final FactTemplate factTemplate) {
274: if (this .factTemplates == Collections.EMPTY_MAP) {
275: this .factTemplates = new HashMap(1);
276: }
277: this .factTemplates.put(factTemplate.getName(), factTemplate);
278: }
279:
280: /**
281: * Add a <code>Rule</code> to this <code>Package</code>.
282: *
283: * @param rule
284: * The rule to add.
285: *
286: * @throws DuplicateRuleNameException
287: * If the <code>Rule</code> attempting to be added has the
288: * same name as another previously added <code>Rule</code>.
289: * @throws InvalidRuleException
290: * If the <code>Rule</code> is not valid.
291: */
292: public void addRule(final Rule rule) {
293: final String name = rule.getName();
294:
295: this .rules.put(name, rule);
296: rule.setLoadOrder(this .rules.size());
297: }
298:
299: /**
300: * Add a rule flow to this package.
301: */
302: public void addRuleFlow(Process process) {
303: if (this .ruleFlows == Collections.EMPTY_MAP) {
304: this .ruleFlows = new HashMap();
305: }
306: this .ruleFlows.put(process.getId(), process);
307: }
308:
309: /**
310: * Get the rule flows for this package. The key is the ruleflow id.
311: * It will be Collections.EMPTY_MAP if none have been added.
312: */
313: public Map getRuleFlows() {
314: return this .ruleFlows;
315: }
316:
317: /**
318: * Rule flows can be removed by ID.
319: */
320: public void removeRuleFlow(String id) {
321: if (!this .ruleFlows.containsKey(id)) {
322: throw new IllegalArgumentException(
323: "The rule flow with id [" + id
324: + "] is not part of this package.");
325: }
326: this .ruleFlows.remove(id);
327: }
328:
329: public PackageCompilationData removeRule(final Rule rule) {
330: this .rules.remove(rule.getName());
331: final String consequenceName = rule.getConsequence().getClass()
332: .getName();
333:
334: // check for compiled code and remove if present.
335: if (this .packageCompilationData.remove(consequenceName)) {
336: removeClasses(rule.getLhs());
337:
338: // Now remove the rule class - the name is a subset of the consequence name
339: this .packageCompilationData.remove(consequenceName
340: .substring(0, consequenceName
341: .indexOf("ConsequenceInvoker")));
342: }
343: return this .packageCompilationData;
344: }
345:
346: private void removeClasses(final ConditionalElement ce) {
347: if (ce instanceof GroupElement) {
348: final GroupElement group = (GroupElement) ce;
349: for (final Iterator it = group.getChildren().iterator(); it
350: .hasNext();) {
351: final Object object = it.next();
352: if (object instanceof ConditionalElement) {
353: removeClasses((ConditionalElement) object);
354: } else if (object instanceof Pattern) {
355: removeClasses((Pattern) object);
356: }
357: }
358: } else if (ce instanceof EvalCondition) {
359: this .packageCompilationData.remove(((EvalCondition) ce)
360: .getEvalExpression().getClass().getName());
361: }
362: }
363:
364: private void removeClasses(final Pattern pattern) {
365: for (final Iterator it = pattern.getConstraints().iterator(); it
366: .hasNext();) {
367: final Object object = it.next();
368: if (object instanceof PredicateConstraint) {
369: this .packageCompilationData
370: .remove(((PredicateConstraint) object)
371: .getPredicateExpression().getClass()
372: .getName());
373: } else if (object instanceof ReturnValueConstraint) {
374: this .packageCompilationData
375: .remove(((ReturnValueConstraint) object)
376: .getExpression().getClass().getName());
377: }
378: }
379: }
380:
381: /**
382: * Retrieve a <code>Rule</code> by name.
383: *
384: * @param name
385: * The name of the <code>Rule</code> to retrieve.
386: *
387: * @return The named <code>Rule</code>, or <code>null</code> if not
388: * such <code>Rule</code> has been added to this
389: * <code>Package</code>.
390: */
391: public Rule getRule(final String name) {
392: return (Rule) this .rules.get(name);
393: }
394:
395: /**
396: * Retrieve all <code>Rules</code> in this <code>Package</code>.
397: *
398: * @return An array of all <code>Rules</code> in this <code>Package</code>.
399: */
400: public Rule[] getRules() {
401: return (Rule[]) this .rules.values().toArray(
402: new Rule[this .rules.size()]);
403: }
404:
405: public PackageCompilationData getPackageCompilationData() {
406: return this .packageCompilationData;
407: }
408:
409: public String toString() {
410: return "[Package name=" + this .name + "]";
411: }
412:
413: /** Once this is called, the package will be marked as invalid */
414: public void setError(final String summary) {
415: this .errorSummary = summary;
416: this .valid = false;
417: }
418:
419: /**
420: * @return true (default) if there are no build/structural problems.
421: */
422: public boolean isValid() {
423: return this .valid;
424: }
425:
426: /** This will throw an exception if the package is not valid */
427: public void checkValidity() {
428: if (!isValid()) {
429: throw new InvalidRulePackage(this .getErrorSummary());
430: }
431: }
432:
433: /**
434: * This will return the error summary (if any) if the package is invalid.
435: */
436: public String getErrorSummary() {
437: return this .errorSummary;
438: }
439:
440: public boolean equals(final Object object) {
441: if (this == object) {
442: return true;
443: }
444:
445: if (object == null || !(object instanceof Package)) {
446: return false;
447: }
448:
449: final Package other = (Package) object;
450:
451: return (this .name.equals(other.name));
452: }
453:
454: public int hashCode() {
455: return this .name.hashCode();
456: }
457:
458: public void clear() {
459: this.rules.clear();
460: this.packageCompilationData.clear();
461: }
462: }
|