001: /*
002: * Copyright 2001 Sun Microsystems, Inc. All rights reserved.
003: * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
004: */
005:
006: package com.sun.portal.search.rdm;
007:
008: import com.sun.portal.search.soif.*;
009:
010: import java.io.*;
011: import java.util.*;
012:
013: /**
014: * RDM Classification
015: *
016: * A Classification (or "Category") is how data is organized into
017: * a taxonomy. A Classification is analogous to a directory or folder
018: * in a file system.
019: *
020: * To load a Classification definition encoded in SOIF from a file or memory,
021: * use RDMTaxonomy_Parse().
022: *
023: * Example:
024: * SOIFStream *ss = SOIF_ParseInitFile(stdin);
025: * c = RDMClassification_Parse(ss);
026: *
027: * To create a basic Classification structure, use RDMClassification_Parse().
028: * To free a Classification (only node, no children or xrefs),
029: * use RDMClassification_Free().
030: */
031: public class RDMClassification implements Comparable {
032:
033: RDMClassification parent; // Pointer up the Taxonomy tree
034: List children; // its children sorted by Id (RDMClassification)
035: // XXX why sorted? - not using SortedSet for fast indexed access
036: List xrefs; // its cross-references sorted by Id (RDMClassification)
037: String subcategory; // subcategory -- id's last component
038: int depth; // number of hops from ROOT
039: int ndescendant; // number of descendants
040: int ndocs; // docs in this class
041: int ndescdocs; // docs in this plus all lower classes
042: SOIF soif; // Direct access to SOIF object
043:
044: public RDMClassification(String id) {
045: soif = new SOIF(RDM.A_SN_RDM_CLASS, null);
046: setSubcategory(id);
047: // children and xrefs are allocated on demand to avoid leaf node baggage
048: //children = new ArrayList();
049: //xrefs = new ArrayList();
050: }
051:
052: public RDMClassification(SOIFInputStream ss) throws Exception {
053: this ((String) null);
054: SOIF s = ss.readSOIF();
055: if (s == null
056: || !s.getSchemaName().equalsIgnoreCase(
057: RDM.A_SN_RDM_CLASS))
058: throw new Exception("invalid classification");
059: setSubcategory(s.getValue(RDM.A_RDM_ID));
060: soif.merge(s);
061: }
062:
063: public void setSubcategory(String id) {
064: if (id != null) {
065: int p;
066: soif.insert(RDM.A_RDM_ID, id);
067: if ((p = id.lastIndexOf(':')) != -1)
068: p++;
069: else
070: p = 0;
071: subcategory = id.substring(p);
072: }
073: }
074:
075: /**
076: * To lookup by classification id, use find().
077: *
078: * Example:
079: * RDMClassification musicp = c.find("Arts:Music");
080: */
081: public RDMClassification find(String id) {
082: int p, target_depth;
083: for (p = 0, target_depth = 0; p != -1; p = id.indexOf(':',
084: p + 1), target_depth++)
085: ; // count colons
086: // start the search starting at our current depth
087: return classfind(id, target_depth);
088: }
089:
090: /**
091: * To insert a new classification that is the child of the current
092: * Inserts a new child, and sorts all children based on Classification Id
093: */
094: public void insertChild(RDMClassification newchild) {
095: RDMClassification walker;
096: if (children == null)
097: children = new ArrayList();
098: children.add(newchild);
099: newchild.depth = depth + 1;
100: Collections.sort(children);
101: int i;
102: // Update the ndescendant statistics
103: for (i = 0, walker = this ; walker != null && i <= depth; ++i, walker = walker.parent)
104: walker.ndescendant++;
105: }
106:
107: /*
108: * 1. find if child exist
109: * 2. count number of descendant
110: * 3. delete the child
111: * 4. update number of descendant for each ascendant
112: */
113: public void deleteChild(String childID) throws Exception {
114: RDMClassification child = find(childID);
115: if (child != null) {
116: RDMClassification parent = child.getParent();
117: if (parent != null) {
118: int childNbDescendant = child.getNumDescendant();
119: int childIndex = children.indexOf(child);
120: children.remove(childIndex);
121: Collections.sort(children);
122:
123: // updating ascendants' num of descendants
124: for (RDMClassification asc = parent; asc != null; asc = asc
125: .getParent()) {
126: asc.ndescendant = asc.ndescendant
127: - (childNbDescendant + 1);
128: }
129: } else {
130: }
131: } else {
132: throw new Exception(
133: "RDMClassification - deleteChild() - child not in current classification");
134: }
135: }
136:
137: /** To insert a cross-reference to another classification */
138: public void insertXref(String newxref) {
139: if (xrefs == null)
140: xrefs = new ArrayList();
141: xrefs.add(newxref);
142: }
143:
144: public void deleteXref(String xref) {
145: // XXX assert(0);
146: }
147:
148: /** To traverse the Taxonomy in the given order starting at this
149: * Classification, use apply(). Similar to RDMTaxonomy::apply().
150: */
151: public void apply(int order, RDMCallback cb) throws Exception {
152: int i, nchildren = nChildren();
153: switch (order) {
154: case RDM.RDM_TAX_POSTORDER:
155: for (i = 0; i < nchildren; i++)
156: nthChild(i).apply(order, cb);
157: cb.callback(this );
158: break;
159: case RDM.RDM_TAX_PREORDER:
160: case RDM.RDM_TAX_INORDER:
161: cb.callback(this );
162: for (i = 0; i < nchildren; i++)
163: nthChild(i).apply(order, cb);
164: break;
165: default:
166: break;
167: }
168: }
169:
170: /** Macros for accessing children and cross references */
171: public RDMClassification nthChild(int n) {
172: return (RDMClassification) children.get(n);
173: }
174:
175: public RDMClassification nthXref(int n) {
176: return (RDMClassification) xrefs.get(n);
177: }
178:
179: public int nChildren() {
180: if (children == null)
181: return 0;
182: return children.size();
183: }
184:
185: public int nXrefs() {
186: if (xrefs == null)
187: return 0;
188: return children.size();
189: }
190:
191: /** Returns the portion of the id that is 'depth' levels deep.
192: * For example,
193: * ("a", 1) -. "a"
194: * ("a", 2) -. NULL
195: * ("a:b", 2) -. "b"
196: * ("a:b:c:d", 1) -. "a"
197: * ("a:b:c:d", 2) -. "b"
198: * ("a:b:c:d", 3) -. "c"
199: * ("a:b:c:d", 4) -. "d"
200: * ("a:b:c:d", 5) -. NULL
201: * ("a:b:c:d", 0) -. NULL
202: *
203: * Return malloc'ed value if successful; otherwise returns NULL;
204: */
205: static public String subcategory(String id, int depth) {
206: String result = null;
207: int subcat_start = 0, subcat_end = 0, p, q;
208:
209: if (depth < 1)
210: return null;
211:
212: if (depth == 1) {
213: if ((p = id.indexOf(':')) != -1) {
214: subcat_end = p;
215: } else {
216: subcat_end = id.length();
217: }
218: } else {
219: // depth > 1
220: int i;
221:
222: // Find the nth subcategory
223: p = 0;
224: for (i = 0; i < depth - 1; i++) {
225: if ((q = id.indexOf(':', p)) == -1) // not enough levels
226: return null;
227: p = ++q;
228: }
229: subcat_start = p;
230: if ((q = id.indexOf(':', p)) != -1) {
231: subcat_end = q;
232: } else {
233: subcat_end = id.length();
234: }
235: }
236: if ((subcat_end - subcat_start) < 1)
237: return null;
238: return id.substring(subcat_start, subcat_end);
239: }
240:
241: /** Macros for accessing @CLASSIFICATION values
242: * Use prototype: char *RDMClassification_GetXXX(RDMClassification *c).
243: */
244: public String getId() {
245: return soif.getValue(RDM.A_RDM_ID);
246: }
247:
248: public String getParentId() {
249: return soif.getValue(RDM.A_RDM_PARENT);
250: }
251:
252: public String getTaxonomyId() {
253: return soif.getValue(RDM.A_RDM_TAX);
254: }
255:
256: public String getDescription() {
257: return soif.getValue(RDM.A_RDM_DESC);
258: }
259:
260: public String getMatchingRule() {
261: return soif.getValue(RDM.A_RDM_MATCHRULE);
262: }
263:
264: public SOIF getSOIF() {
265: return soif;
266: }
267:
268: public int getNumDocs() {
269: return ndocs;
270: }
271:
272: public int getNumDescDocs() {
273: return ndescdocs;
274: }
275:
276: public int getNumDescendant() {
277: return ndescendant;
278: }
279:
280: public void setNumDescendant(int nb) {
281: ndescendant = nb;
282: }
283:
284: public int getDepth() {
285: return depth;
286: }
287:
288: public List getChildren() {
289: return children;
290: }
291:
292: public RDMClassification getParent() {
293: return parent;
294: }
295:
296: public String getSubcategory() {
297: return subcategory;
298: }
299:
300: /** Macros for defining @CLASSIFICATION values
301: * Use prototype: int RDMClassification_SetXXX(RDMClassification *c,
302: * char *newval).
303: */
304: public void setId(String s) {
305: soif.replace(RDM.A_RDM_ID, s);
306: }
307:
308: public void setParentId(String s) {
309: soif.replace(RDM.A_RDM_PARENT, s);
310: }
311:
312: public void setTaxonomyId(String s) {
313: soif.replace(RDM.A_RDM_TAX, s);
314: }
315:
316: public void setDescription(String s) {
317: soif.replace(RDM.A_RDM_DESC, s);
318: }
319:
320: public void setMatchingRule(String s) {
321: soif.replace(RDM.A_RDM_MATCHRULE, s);
322: }
323:
324: public void setNumDocs(int n) {
325: ndocs = n;
326: }
327:
328: public void setNumDescDocs(int n) {
329: ndescdocs = n;
330: }
331:
332: protected RDMClassification classfind(String target_id,
333: int target_depth) {
334: int i, nchildren;
335: String subcat = null;
336:
337: // If we're at the desired depth, just look for the classification
338: if (depth == target_depth) {
339: if (getId().equals(target_id)) {
340: return this ;
341: }
342: return null;
343: }
344:
345: // If we're too deep in the tree, stop; this shouldn't happen
346: if (depth > target_depth) {
347: return null;
348: }
349:
350: /**
351: * If we're higher than the desired depth, then make a routing
352: * decision. Only decend into the child which has the matching
353: * subcategory
354: */
355: if ((subcat = subcategory(target_id, depth + 1)) == null) {
356: return null;
357: }
358:
359: for (i = 0, nchildren = nChildren(); i < nchildren; i++) {
360: RDMClassification child = nthChild(i);
361: if (child != null && child.subcategory != null
362: && child.subcategory.equals(subcat)) { // exact match
363: return child.classfind(target_id, target_depth);
364: }
365: }
366: return null;
367: }
368:
369: public String toString() {
370: // dumping children SOIFs
371: return toString(this );
372:
373: }
374:
375: private String toString(RDMClassification rc) {
376: try {
377: StringBuffer sb = new StringBuffer();
378:
379: // dumping current
380: sb.append(new String(rc.getSOIF().toByteArray()));
381: // dumping childrens
382: if (rc.getNumDescendant() != 0) {
383: // adding separator
384: sb.append("\n");
385: // loop on dumping all the classification
386: for (int j = 0; j < rc.getChildren().size(); j++) {
387: sb.append(toString(rc.nthChild(j)));
388: }
389: }
390: return sb.toString();
391: } catch (IOException ioe) {
392: return null;
393: }
394: }
395:
396: // for sorting
397: public int compareTo(Object obj) {
398: return subcategory
399: .compareToIgnoreCase(((RDMClassification) obj).subcategory);
400: }
401:
402: }
|