001: /******************************************************************************
002: * JBoss, a division of Red Hat *
003: * Copyright 2006, Red Hat Middleware, LLC, and individual *
004: * contributors as indicated by the @authors tag. See the *
005: * copyright.txt in the distribution for a full listing of *
006: * individual contributors. *
007: * *
008: * This is free software; you can redistribute it and/or modify it *
009: * under the terms of the GNU Lesser General Public License as *
010: * published by the Free Software Foundation; either version 2.1 of *
011: * the License, or (at your option) any later version. *
012: * *
013: * This software is distributed in the hope that it will be useful, *
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of *
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
016: * Lesser General Public License for more details. *
017: * *
018: * You should have received a copy of the GNU Lesser General Public *
019: * License along with this software; if not, write to the Free *
020: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
021: * 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
022: ******************************************************************************/package org.jboss.portal.core.model.portal;
023:
024: import org.jboss.portal.common.util.Base64;
025: import org.jboss.portal.common.util.ParameterValidation;
026: import org.jboss.portal.common.util.Tools;
027:
028: import java.io.Serializable;
029: import java.io.UnsupportedEncodingException;
030: import java.util.Iterator;
031:
032: /**
033: * A path for a portal object.
034: *
035: * @author <a href="mailto:julien@jboss.org">Julien Viet</a>
036: * @author <a href="mailto:theute@jboss.org">Thomas Heute</a>
037: * @version $Revision: 8786 $
038: */
039: public class PortalObjectPath implements Comparable, Serializable {
040:
041: /** . */
042: private static final String[] EMPTY_STRINGS = new String[0];
043:
044: /** This statement must be executed before the previous one otherwise the empty string array will be null. */
045: public static final PortalObjectPath ROOT_PATH = new PortalObjectPath();
046:
047: /** The composites. */
048: private final String[] names;
049:
050: /** Inclusive start index. */
051: private final int from;
052:
053: /** Exclusive stop index. */
054: private final int to;
055:
056: /** The lazy computed hash code. */
057: private Integer hashCode;
058:
059: /** The lazy computed to string value for canonical format. */
060: private String toStringCanonicalFormat;
061:
062: /** The lazy computed to string value for legacy format. */
063: private String toStringLegacyFormat;
064:
065: /** The lazy created parent that can be useful. */
066: private PortalObjectPath parent;
067:
068: public PortalObjectPath() {
069: this .names = EMPTY_STRINGS;
070: this .from = 0;
071: this .to = 0;
072: }
073:
074: /**
075: * Copy constructor.
076: *
077: * @param that the id to clone
078: * @throws IllegalArgumentException if the argument to clone is null
079: */
080: public PortalObjectPath(PortalObjectPath that)
081: throws IllegalArgumentException {
082: ParameterValidation.throwIllegalArgExceptionIfNull(that,
083: "PortalObjectId to be copied");
084:
085: //
086: this .names = that.names;
087: this .hashCode = that.hashCode;
088: this .from = that.from;
089: this .to = that.to;
090: }
091:
092: /**
093: * Build an id directly from its composing names.
094: *
095: * @param names the id names
096: * @throws IllegalArgumentException if any argument is null or not well formed
097: */
098: public PortalObjectPath(String[] names)
099: throws IllegalArgumentException {
100: ParameterValidation.throwIllegalArgExceptionIfNull(names,
101: "composing names");
102:
103: //
104: this .names = names;
105: this .from = 0;
106: this .to = names.length;
107: }
108:
109: /**
110: * Build an id directly from its composing names.
111: *
112: * @param names the id names
113: * @throws IllegalArgumentException if any argument is null or not well formed
114: */
115: private PortalObjectPath(String[] names, int from, int to)
116: throws IllegalArgumentException {
117: ParameterValidation.throwIllegalArgExceptionIfNull(names,
118: "composing names");
119:
120: //
121: this .names = names;
122: this .from = from;
123: this .to = to;
124: }
125:
126: /**
127: * Return the parent or null if this is the root id.
128: *
129: * @return the parent
130: */
131: public PortalObjectPath getParent() {
132: if (parent == null && from < to) {
133: parent = new PortalObjectPath(names, from, to - 1);
134: }
135: return parent;
136: }
137:
138: public PortalObjectPath getChild(String name) {
139: int length = to - from;
140: String[] childNames = new String[length + 1];
141: System.arraycopy(this .names, from, childNames, 0, length);
142: childNames[length] = name;
143: return new PortalObjectPath(childNames);
144: }
145:
146: /**
147: * Build an id by parsing a string representation.
148: *
149: * @param value the string representation
150: * @param format the string format
151: * @throws IllegalArgumentException if any argument is null or not well formed
152: */
153: public PortalObjectPath(String value, PortalObjectPath.Format format)
154: throws IllegalArgumentException {
155: ParameterValidation.throwIllegalArgExceptionIfNull(format,
156: "Format");
157:
158: //
159: this .names = format.parse(value);
160: this .from = 0;
161: this .to = names.length;
162: }
163:
164: public int getLength() {
165: return to - from;
166: }
167:
168: public String getName(int index) {
169: if (index < from) {
170: throw new IllegalArgumentException();
171: }
172: if (index >= to) {
173: throw new IllegalArgumentException();
174: }
175: return names[index - from];
176: }
177:
178: public boolean equals(Object obj) {
179: if (obj == this ) {
180: return true;
181: }
182: if (obj instanceof PortalObjectPath) {
183: PortalObjectPath that = (PortalObjectPath) obj;
184: String[] this Names = this .names;
185: String[] thatNames = that.names;
186:
187: //
188: //
189: int this Len = this .to - this .from;
190: int thatLen = that.to - that.from;
191: if (this Len != thatLen) {
192: return false;
193: }
194:
195: //
196: int this Idx = this .from;
197: int thatIdx = that.from;
198: while (this Len-- > 0) {
199: if (!this Names[this Idx++].equals(thatNames[thatIdx++])) {
200: return false;
201: }
202: }
203:
204: //
205: return true;
206: }
207: return false;
208: }
209:
210: public int hashCode() {
211: if (hashCode == null) {
212: int value = 0;
213: for (int i = to - 1; i >= from; i--) {
214: value = value * 41 + names[i].hashCode();
215: }
216: hashCode = new Integer(value);
217: }
218: return hashCode.intValue();
219: }
220:
221: /** Lexicographical order based implementation on the names atoms. */
222: public int compareTo(Object o) {
223: PortalObjectPath that = (PortalObjectPath) o;
224: int index = 0;
225: while (index < this .names.length && index < that.names.length) {
226: String this Name = this .names[index];
227: String thatName = that.names[index];
228: int order = this Name.compareTo(thatName);
229: if (order != 0) {
230: return order;
231: }
232: index++;
233: }
234: return that.names.length - this .names.length;
235: }
236:
237: /**
238: * Return an iterator over the different names.
239: *
240: * @return the iterator over the names
241: */
242: public Iterator names() {
243: return Tools.iterator(names, from, to);
244: }
245:
246: /**
247: * Returns the canonical representation.
248: *
249: * @return the string value
250: */
251: public String toString() {
252: return toString(CANONICAL_FORMAT);
253: }
254:
255: /**
256: * Returns a string representation using a specified format
257: *
258: * @param format the output format
259: * @return the string value
260: */
261: public String toString(PortalObjectPath.Format format) {
262: ParameterValidation.throwIllegalArgExceptionIfNull(format,
263: "Format");
264:
265: //
266: if (format == LEGACY_FORMAT) {
267: if (toStringLegacyFormat == null) {
268: toStringLegacyFormat = LEGACY_FORMAT.toString(names,
269: from, to);
270: }
271: return toStringLegacyFormat;
272: } else if (format == CANONICAL_FORMAT) {
273: if (toStringCanonicalFormat == null) {
274: toStringCanonicalFormat = CANONICAL_FORMAT.toString(
275: names, from, to);
276: }
277: return toStringCanonicalFormat;
278: }
279:
280: //
281: return format.toString(names, from, to);
282: }
283:
284: public static PortalObjectPath parse(String value,
285: PortalObjectPath.Format format) {
286: return new PortalObjectPath(value, format);
287: }
288:
289: /** The format of a string representation of an id. */
290: public abstract static class Format {
291: /**
292: * @param value
293: * @return
294: */
295: public abstract String[] parse(String value)
296: throws IllegalArgumentException;
297:
298: /**
299: * @param names
300: * @return
301: */
302: public abstract String toString(String[] names, int from, int to)
303: throws IllegalArgumentException;
304:
305: /**
306: * @param id
307: * @return
308: */
309: public final String toString(PortalObjectPath id)
310: throws IllegalArgumentException {
311: ParameterValidation.throwIllegalArgExceptionIfNull(id,
312: "PortalObjectId");
313:
314: //
315: return toString(id.names, id.from, id.to);
316: }
317: }
318:
319: /** Canonical format, smth like /a/b/c. */
320: public static final PortalObjectPath.Format CANONICAL_FORMAT = new CanonicalFormat();
321:
322: public static final class CanonicalFormat extends Format {
323: public static final char PATH_SEPARATOR = '/';
324:
325: public String[] parse(String value) {
326: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
327: value, "value", "Format.parse(value)");
328: if (value.charAt(0) != PATH_SEPARATOR) {
329: throw new IllegalArgumentException(
330: "Not a canonical value " + value);
331: }
332: if (value.length() == 1) {
333: return new String[0];
334: }
335:
336: //
337: int length = 1;
338: int previous = 1;
339: while (true) {
340: int next = value.indexOf(PATH_SEPARATOR, previous);
341: if (next == -1) {
342: break;
343: }
344: if (next > 0 && value.charAt(next - 1) != '\\') {
345: length++;
346: }
347: previous = next + 1;
348: }
349:
350: //
351: String[] names = new String[length];
352: length = 0;
353: previous = 1;
354: int next = 1;
355: while (true) {
356: next = value.indexOf(PATH_SEPARATOR, next);
357: if (next == -1) {
358: break;
359: }
360: if (next > 0 && value.charAt(next - 1) != '\\') {
361: String name = value.substring(previous, next);
362: name = decodeName(name);
363: names[length++] = name;
364: previous = next + 1;
365: }
366: next++;
367: }
368: String name = value.substring(previous);
369: name = decodeName(name);
370: names[length] = name;
371:
372: //
373: return names;
374: }
375:
376: public String toString(String[] names, int from, int to) {
377: ParameterValidation.throwIllegalArgExceptionIfNull(names,
378: "name string array");
379: if (from == to) {
380: return "" + PATH_SEPARATOR;
381: } else {
382: StringBuffer tmp = new StringBuffer(names.length * 10);
383: for (int i = from; i < to; i++) {
384: String name = names[i];
385: if (name == null) {
386: throw new IllegalArgumentException(
387: "No null name expected in the name string array");
388: }
389: name = encodeName(name);
390: tmp.append(PATH_SEPARATOR).append(name);
391: }
392: return tmp.toString();
393: }
394: }
395:
396: protected String decodeName(String name) {
397: return name.replaceAll("\\/", "/");
398: }
399:
400: protected String encodeName(String name) {
401: return name.replaceAll("/", "\\/");
402: }
403:
404: }
405:
406: ;
407:
408: /** The internal format when it is persisted, smth like a.b.c . */
409: public static final PortalObjectPath.Format LEGACY_FORMAT = new PortalObjectPath.LegacyFormat();
410:
411: public static class LegacyFormat extends PortalObjectPath.Format {
412:
413: /** . */
414: private final String[] EMPTY_STRING_ARRAY = new String[0];
415:
416: public String[] parse(String value) {
417: ParameterValidation.throwIllegalArgExceptionIfNull(value,
418: "value");
419:
420: //
421: if (value.length() == 0) {
422: return EMPTY_STRING_ARRAY;
423: }
424:
425: // Count the number of names
426: int length = 1;
427: for (int next = value.indexOf('.'); next != -1; next = value
428: .indexOf('.', next + 1)) {
429: if (next > 0 && value.charAt(next - 1) != '\\') {
430: length++;
431: }
432: }
433:
434: //
435: String[] names = new String[length];
436: length = 0;
437: int previous = 0;
438: for (int next = value.indexOf('.'); next != -1; next = value
439: .indexOf('.', next + 1)) {
440: if (next > 0 && value.charAt(next - 1) != '\\') {
441: String name = value.substring(previous, next);
442: name = decodeName(name);
443: names[length++] = name;
444: previous = next + 1;
445: }
446: }
447: String name = value.substring(previous);
448: name = decodeName(name);
449: names[length] = name;
450:
451: //
452: return names;
453: }
454:
455: public String toString(String[] names, int from, int to) {
456: ParameterValidation.throwIllegalArgExceptionIfNull(names,
457: "name string array");
458:
459: //
460: if (from == to) {
461: return "";
462: }
463:
464: //
465: StringBuffer buffer = new StringBuffer((to - from) * 8);
466: for (int i = from; i < to; i++) {
467: if (i > 0) {
468: buffer.append('.');
469: }
470: String name = names[i];
471: if (name == null) {
472: throw new IllegalArgumentException(
473: "No null name expected in the name string array");
474: }
475: name = encodeName(name);
476: buffer.append(name);
477: }
478: return buffer.toString();
479: }
480:
481: protected String decodeName(String name) {
482: return name.replaceAll("\\\\\\.", ".");
483: }
484:
485: protected String encodeName(String name) {
486: return name.replaceAll("\\.", "\\\\\\.");
487: }
488:
489: }
490:
491: public static final PortalObjectPath.Format LEGACY_BASE64_FORMAT = new PortalObjectPath.LegacyFormat() {
492:
493: protected String decodeName(String name) {
494: try {
495: byte[] bytes = Base64.decode(name);
496: return new String(bytes, "UTF-8");
497: } catch (UnsupportedEncodingException e) {
498: throw new Error(e);
499: }
500: }
501:
502: protected String encodeName(String name) {
503: try {
504: byte[] bytes = name.getBytes("UTF-8");
505: name = Base64.encodeBytes(bytes);
506: return name;
507: } catch (UnsupportedEncodingException e) {
508: throw new Error(e);
509: }
510: }
511: };
512: }
|