001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.compass.core.mapping;
018:
019: import java.util.ArrayList;
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.compass.core.converter.ConverterLookup;
027: import org.compass.core.engine.naming.PropertyPath;
028: import org.compass.core.mapping.osem.ClassMapping;
029: import org.compass.core.mapping.rsem.RawResourceMapping;
030:
031: /**
032: * @author kimchy
033: */
034: public class CompassMapping {
035:
036: private final Map<String, AliasMapping> mappings = new HashMap<String, AliasMapping>();
037:
038: private final Map<String, ResourceMapping> rootMappingsByAlias = new HashMap<String, ResourceMapping>();
039:
040: private final Map<String, ResourceMapping> nonRootMappingsByAlias = new HashMap<String, ResourceMapping>();
041:
042: private final ResourceMappingsByNameHolder mappingsByClass = new ResourceMappingsByNameHolder();
043:
044: private final ResourceMappingsByNameHolder cachedMappingsByClass = new ResourceMappingsByNameHolder();
045:
046: private final ResourceMappingsByNameHolder rootMappingsByClass = new ResourceMappingsByNameHolder();
047:
048: private final ResourceMappingsByNameHolder cachedRootMappingsByClass = new ResourceMappingsByNameHolder();
049:
050: private final ResourceMappingsByNameHolder nonRootMappingsByClass = new ResourceMappingsByNameHolder();
051:
052: private final ResourceMappingsByNameHolder cachedNonRootMappingsByClass = new ResourceMappingsByNameHolder();
053:
054: private ResourceMapping[] rootMappingsArr = new ResourceMapping[0];
055:
056: private ConverterLookup converterLookup;
057:
058: private final NullResourceMapping nullResourceMappingEntryInCache = new NullResourceMapping();
059:
060: private final HashMap<String, ResourcePropertyMapping[]> resourcePropertyMappingByPath = new HashMap<String, ResourcePropertyMapping[]>();
061:
062: private PropertyPath path;
063:
064: public CompassMapping() {
065: }
066:
067: public CompassMapping copy(ConverterLookup converterLookup) {
068: CompassMapping copy = new CompassMapping();
069: copy.converterLookup = converterLookup;
070: copy.setPath(getPath());
071: for (AliasMapping aliasMapping : mappings.values()) {
072: AliasMapping copyMapping = (AliasMapping) (aliasMapping)
073: .copy();
074: copy.addMapping(copyMapping);
075: }
076: return copy;
077: }
078:
079: public void postProcess() {
080: ResourceMapping[] rootMappings = getRootMappings();
081: for (ResourceMapping rootMapping : rootMappings) {
082: // update the resource property mapping
083: ResourcePropertyMapping[] resourcePropertyMappings = rootMapping
084: .getResourcePropertyMappings();
085: for (ResourcePropertyMapping rpm : resourcePropertyMappings) {
086: if (rpm.getPath() != null) {
087: String path = rpm.getPath().getPath();
088: ResourcePropertyMapping[] rpms = resourcePropertyMappingByPath
089: .get(path);
090: if (rpms == null) {
091: resourcePropertyMappingByPath.put(path,
092: new ResourcePropertyMapping[] { rpm });
093: } else {
094: ResourcePropertyMapping[] tmpRpms = new ResourcePropertyMapping[rpms.length + 1];
095: System.arraycopy(rpms, 0, tmpRpms, 0,
096: rpms.length);
097: tmpRpms[tmpRpms.length - 1] = rpm;
098: resourcePropertyMappingByPath
099: .put(path, tmpRpms);
100: }
101: }
102: }
103: }
104: }
105:
106: public void clearMappings() {
107: mappings.clear();
108:
109: rootMappingsByAlias.clear();
110: nonRootMappingsByAlias.clear();
111:
112: mappingsByClass.clear();
113: cachedMappingsByClass.clear();
114:
115: rootMappingsByClass.clear();
116: cachedRootMappingsByClass.clear();
117:
118: nonRootMappingsByClass.clear();
119: cachedNonRootMappingsByClass.clear();
120:
121: rootMappingsArr = new ResourceMapping[0];
122:
123: resourcePropertyMappingByPath.clear();
124: }
125:
126: public void addMapping(AliasMapping mapping)
127: throws MappingException {
128: if (mappings.get(mapping.getAlias()) != null) {
129: throw new MappingException(
130: "Compass does not allow multiple aliases for alias ["
131: + mapping.getAlias() + "]");
132: }
133: mappings.put(mapping.getAlias(), mapping);
134: if (mapping instanceof ResourceMapping) {
135: ResourceMapping resourceMapping = (ResourceMapping) mapping;
136: if (resourceMapping.isRoot()) {
137: rootMappingsByAlias.put(mapping.getAlias(),
138: resourceMapping);
139: if (resourceMapping instanceof ClassMapping) {
140: rootMappingsByClass.addMapping(resourceMapping
141: .getName(), resourceMapping);
142: mappingsByClass.addMapping(resourceMapping
143: .getName(), resourceMapping);
144:
145: }
146: ResourceMapping[] result = new ResourceMapping[rootMappingsArr.length + 1];
147: int i;
148: for (i = 0; i < rootMappingsArr.length; i++) {
149: result[i] = rootMappingsArr[i];
150: }
151: result[i] = resourceMapping;
152: rootMappingsArr = result;
153: } else {
154: nonRootMappingsByAlias.put(mapping.getAlias(),
155: resourceMapping);
156: if (resourceMapping instanceof ClassMapping) {
157: mappingsByClass.addMapping(resourceMapping
158: .getName(), resourceMapping);
159: nonRootMappingsByClass.addMapping(resourceMapping
160: .getName(), resourceMapping);
161: }
162: }
163: }
164: }
165:
166: /**
167: * Returns a resoruce lookup for a specific name. Supports dot path notation ([alias].[class property].).
168: * Allows to get the meta-data/resource property mapping through it (or a list of mappings).
169: */
170: public ResourcePropertyLookup getResourcePropertyLookup(String name)
171: throws IllegalArgumentException {
172: return new ResourcePropertyLookup(this , name);
173: }
174:
175: /**
176: * Finds the {@link ResourcePropertyMapping} definition for the specified path. The
177: * path is in the format of: [alias].[class property mapping].[meta data mapping] in
178: * case of class mapping, and [alias].[resource property mapping] in case of resource
179: * mapping. The format of [alias].[class property mapping] can also be applied, and
180: * will result in the meta data id of the given class property mapping.
181: *
182: * @param path the path to the resource property mapping
183: * @return the resource property mapping for the given path
184: */
185: public ResourcePropertyMapping getResourcePropertyMappingByPath(
186: String path) {
187: int dotIndex = path.indexOf('.');
188: if (dotIndex == -1) {
189: return null;
190: }
191: String alias = path.substring(0, dotIndex);
192: ResourceMapping resourceMapping = getRootMappingByAlias(alias);
193: if (resourceMapping == null) {
194: throw new IllegalArgumentException(
195: "Failed to find class/resource mapping for alias ["
196: + alias + "] from path [" + path + "]");
197: }
198: ResourcePropertyMapping resourcePropertyMapping = resourceMapping
199: .getResourcePropertyMappingByDotPath(path
200: .substring(dotIndex + 1));
201: if (resourcePropertyMapping == null) {
202: throw new IllegalArgumentException(
203: "Failed to find mapping for alias [" + alias
204: + "] and path [" + path + "]");
205: }
206: return resourcePropertyMapping;
207: }
208:
209: /**
210: * Returns an array of all the given {@link org.compass.core.mapping.ResourcePropertyMapping} for the given
211: * path. If the path is in "dot path" notation, will reutrn a single mappings matching it (see
212: * {@link #getResourcePropertyMappingByPath(String)}). Otherwise will return all the ones mapped to
213: * the given name.
214: */
215: public ResourcePropertyMapping[] getResourcePropertyMappingsByPath(
216: String path) {
217: int dotIndex = path.indexOf('.');
218: if (dotIndex != -1) {
219: return new ResourcePropertyMapping[] { getResourcePropertyMappingByPath(path) };
220: }
221: return resourcePropertyMappingByPath.get(path);
222: }
223:
224: /**
225: * Returns an itertor over all the current mappings.
226: */
227: public Iterator mappingsIt() {
228: return mappings.values().iterator();
229: }
230:
231: /**
232: * Returns the alias mapping for the given alias (most if not all of the times, this will be a {@link org.compass.core.mapping.ResourceMapping}).
233: */
234: public AliasMapping getAliasMapping(String alias) {
235: return mappings.get(alias);
236: }
237:
238: /**
239: * Returns the resource mapping for the given alias.
240: */
241: public ResourceMapping getMappingByAlias(String alias) {
242: return (ResourceMapping) mappings.get(alias);
243: }
244:
245: /**
246: * Returns the root resource mapping associated with the alias. Retruns
247: * <code>null</code> if no root mapping (or no mapping) is associated with the alias.
248: */
249: public ResourceMapping getRootMappingByAlias(String alias) {
250: return rootMappingsByAlias.get(alias);
251: }
252:
253: /**
254: * Returns the non root resource mapping associated with the alias. Retruns
255: * <code>null</code> if no non root mapping (or no mapping) is associated with the alias.
256: */
257: public ResourceMapping getNonRootMappingByAlias(String alias) {
258: return nonRootMappingsByAlias.get(alias);
259: }
260:
261: /**
262: * Returns <code>true</code> if the given alias has a root resource mapping.
263: */
264: public boolean hasRootMappingByAlias(String alias) {
265: return rootMappingsByAlias.get(alias) != null;
266: }
267:
268: /**
269: * Returns <code>true</code> if there is a <b>root</b> {@link org.compass.core.mapping.osem.ClassMapping}
270: * for the given alias.
271: */
272: public boolean hasRootClassMapping(String alias) {
273: return (rootMappingsByAlias.get(alias) instanceof ClassMapping);
274: }
275:
276: /**
277: * Returns <code>true</code> if there is a <b>root</b> {@link org.compass.core.mapping.rsem.RawResourceMapping}
278: * for the given alias.
279: */
280: public boolean hasRootRawResourceMapping(String alias) {
281: return (rootMappingsByAlias.get(alias) instanceof RawResourceMapping);
282: }
283:
284: /**
285: * Returns <code>true</code> if the given <b>className</b> has multiple class mappings.
286: */
287: public boolean hasMultipleRootClassMapping(String className) {
288: return rootMappingsByClass.hasMultipleMappingsByName(className);
289: }
290:
291: /**
292: * Returns the direct class mapping for the given class (root or not). Will not try to
293: * navigate up the interface/superclass in order to find the "nearset" class mapping.
294: *
295: * <p>If a class has more than one mappings (using differnet aliases) will return the
296: * first one.
297: */
298: public ResourceMapping getDirectMappingByClass(Class clazz) {
299: return mappingsByClass
300: .getResourceMappingByName(clazz.getName());
301: }
302:
303: /**
304: * Returns all the direct class mapping for the given class (root or not). Will not
305: * try to navigate up the interface/superclass in order to find the "nearest" class
306: * mapping.
307: */
308: public List<ResourceMapping> getAllDirectMappingByClass(Class clazz) {
309: return mappingsByClass.getUnmodifiableMappingsByName(clazz
310: .getName());
311: }
312:
313: /**
314: * Finds the Resource mapping that is the "nearest" to the provided class.
315: * Similar way that {@link #findRootMappingByClass(Class)} except the search
316: * is on all the ClassMappings (even ones that are not marked as root).
317: */
318: public ResourceMapping getMappingByClass(Class clazz) {
319: return doGetResourceMappingByClass(clazz, false,
320: mappingsByClass, cachedMappingsByClass);
321: }
322:
323: /**
324: * Finds a root mapping by the class name. If a root mapping is not found
325: * for the class name, than searches for mappings for the interfaces, if not
326: * found, checks for subclasses, and subclassess interfaces. Note: If there
327: * is no direct mapping that match the class name, then the mapping that is
328: * found should be marked as poly.
329: *
330: * @param clazz The class to find root mapping for
331: * @return The resource mapping
332: */
333: public ResourceMapping findRootMappingByClass(Class clazz)
334: throws MappingException {
335: return doGetResourceMappingByClass(clazz, true,
336: rootMappingsByClass, cachedRootMappingsByClass);
337: }
338:
339: /**
340: * Does exactly the same as {@link #findRootMappingByClass(Class)}, but returns <code>null</code>
341: * if nothing is found (does not throw an exception).
342: */
343: public ResourceMapping getRootMappingByClass(Class clazz)
344: throws MappingException {
345: return doGetResourceMappingByClass(clazz, false,
346: rootMappingsByClass, cachedRootMappingsByClass);
347: }
348:
349: /**
350: * Finds a non root mapping by the class name. If a non root mapping is not found
351: * for the class name, than searches for mappings for the interfaces, if not
352: * found, checks for subclasses, and subclassess interfaces. Note: If there
353: * is no direct mapping that match the class name, then the mapping that is
354: * found should be marked as poly.
355: *
356: * @param clazz The class to find root mapping for
357: * @return The resource mapping
358: */
359: public ResourceMapping findNonRootMappingByClass(Class clazz)
360: throws MappingException {
361: return doGetResourceMappingByClass(clazz, true,
362: nonRootMappingsByClass, cachedNonRootMappingsByClass);
363: }
364:
365: /**
366: * Does exactly the same as {@link #findNonRootMappingByClass(Class)}, but returns <code>null</code>
367: * if nothing is found (does not throw an exception).
368: */
369: public ResourceMapping getNonRootMappingByClass(Class clazz)
370: throws MappingException {
371: return doGetResourceMappingByClass(clazz, false,
372: nonRootMappingsByClass, cachedNonRootMappingsByClass);
373: }
374:
375: private ResourceMapping doGetResourceMappingByClass(Class clazz,
376: boolean throwEx,
377: ResourceMappingsByNameHolder mappingByClass,
378: ResourceMappingsByNameHolder cachedMappingsByClass)
379: throws MappingException {
380: // we don't really care that we might execute it twice (for caching)
381: String className = clazz.getName();
382: ResourceMapping rm = cachedMappingsByClass
383: .getResourceMappingByName(className);
384: if (rm != null) {
385: if (rm == nullResourceMappingEntryInCache) {
386: if (throwEx) {
387: throw new MappingException(
388: "Failed to find any mappings for class ["
389: + className + "]");
390: }
391: return null;
392: }
393: return rm;
394: }
395: rm = doGetActualResourceMappingByClass(clazz, mappingByClass);
396: if (rm == null) {
397: cachedMappingsByClass.addMapping(className,
398: nullResourceMappingEntryInCache);
399: if (throwEx) {
400: throw new MappingException(
401: "Failed to find any mappings for class ["
402: + className + "]");
403: }
404: return null;
405: } else {
406: cachedMappingsByClass.addMapping(className, rm);
407: }
408: return rm;
409: }
410:
411: private ResourceMapping doGetActualResourceMappingByClass(
412: Class clazz, ResourceMappingsByNameHolder mappingByClass) {
413: ResourceMapping rm = mappingByClass
414: .getResourceMappingByName(clazz.getName());
415: if (rm != null) {
416: return rm;
417: }
418: for (Class anInterface : clazz.getInterfaces()) {
419: rm = mappingByClass.getResourceMappingByName(anInterface
420: .getName());
421: if (rm != null) {
422: return rm;
423: }
424: }
425: Class super Class = clazz.getSuperclass();
426: if (super Class == null) {
427: return null;
428: }
429: return doGetActualResourceMappingByClass(super Class,
430: mappingByClass);
431: }
432:
433: public ResourceMapping[] getRootMappings() {
434: return rootMappingsArr;
435: }
436:
437: public ConverterLookup getConverterLookup() {
438: return converterLookup;
439: }
440:
441: public PropertyPath getPath() {
442: return path;
443: }
444:
445: public void setPath(PropertyPath path) {
446: this .path = path;
447: }
448:
449: /**
450: * A resource mapping holder based on a name (actually, any string). Holds a map keyed
451: * by the name and the value a list of ResourceMapping registered under the name
452: */
453: private class ResourceMappingsByNameHolder {
454:
455: private final HashMap<String, List<ResourceMapping>> mappings = new HashMap<String, List<ResourceMapping>>();
456:
457: void addMapping(String name, ResourceMapping resourceMapping) {
458: List<ResourceMapping> l = mappings.get(name);
459: if (l == null) {
460: l = new ArrayList<ResourceMapping>();
461: mappings.put(name, l);
462: }
463: l.add(resourceMapping);
464: }
465:
466: public void clear() {
467: mappings.clear();
468: }
469:
470: public List<ResourceMapping> getMappingsByName(String name) {
471: return mappings.get(name);
472: }
473:
474: public List<ResourceMapping> getUnmodifiableMappingsByName(
475: String name) {
476: return Collections.unmodifiableList(mappings.get(name));
477: }
478:
479: /**
480: * Returns the first class mapping matching the given name. Returns
481: * <code>null</code> of no mapping matches the name.
482: */
483: public ResourceMapping getResourceMappingByName(String name) {
484: List<ResourceMapping> l = getMappingsByName(name);
485: if (l == null) {
486: return null;
487: }
488: return l.get(l.size() - 1);
489: }
490:
491: public boolean hasMultipleMappingsByName(String name) {
492: List<ResourceMapping> l = getMappingsByName(name);
493: return l != null && l.size() > 1;
494: }
495: }
496: }
|