001: /*
002: * Created on 07-May-2006
003: */
004: package uk.org.ponder.rsf.viewstate.support;
005:
006: import java.util.Map;
007:
008: import uk.org.ponder.arrayutil.ListUtil;
009: import uk.org.ponder.beanutil.PathUtil;
010: import uk.org.ponder.reflect.ReflectiveCache;
011: import uk.org.ponder.rsf.viewstate.SimpleViewParameters;
012: import uk.org.ponder.rsf.viewstate.ViewParamUtil;
013: import uk.org.ponder.rsf.viewstate.ViewParameters;
014: import uk.org.ponder.saxalizer.AccessMethod;
015: import uk.org.ponder.saxalizer.MethodAnalyser;
016: import uk.org.ponder.saxalizer.SAXAccessMethod;
017: import uk.org.ponder.saxalizer.SAXalizerMappingContext;
018: import uk.org.ponder.stringutil.CharWrap;
019: import uk.org.ponder.stringutil.StringList;
020: import uk.org.ponder.util.UniversalRuntimeException;
021:
022: /**
023: * Manages inferred information relating to the mapping of ViewParameters
024: * objects onto a URL structure.<br/> <b>WARNING:</b> <i>NEVER</i> place RSF
025: * at a shared classloader level or else this implementation will leak. The
026: * performance implications of WeakHashMap are too awful to allow any other
027: * approach - ViewParameters are parsed <b>extremely</b> frequently during a
028: * typical request.
029: *
030: * @author Antranig Basman (amb26@ponder.org.uk)
031: * @author aaronz
032: */
033:
034: public class ViewParamsMappingInfoManager {
035: // a map from Class to ViewParamsMapInfo
036: private Map viewparamsmap;
037:
038: public void setReflectiveCache(ReflectiveCache reflectivecache) {
039: viewparamsmap = reflectivecache.getConcurrentMap(1);
040: }
041:
042: private SAXalizerMappingContext mappingcontext;
043:
044: public void setSAXalizerMappingContext(
045: SAXalizerMappingContext mappingcontext) {
046: this .mappingcontext = mappingcontext;
047: }
048:
049: /**
050: * This fetches a cached copy of a VPMI (or creates a new one and caches it if
051: * none exists already) for a given VP
052: *
053: * @param viewparams a VP object
054: * @return a VPMI for this VP object
055: */
056: public ConcreteViewParamsMapInfo getMappingInfo(
057: ViewParameters viewparams) {
058: Class clazz = viewparams.getClass();
059: ConcreteViewParamsMapInfo togo = (ConcreteViewParamsMapInfo) viewparamsmap
060: .get(clazz);
061: if (togo == null) {
062: togo = computeMappingInfo(viewparams);
063: viewparamsmap.put(clazz, togo);
064: }
065: return togo;
066: }
067:
068: private void appendDeclaredFields(CharWrap cw, Class clazz) {
069: MethodAnalyser methodAnalyser = mappingcontext
070: .getAnalyser(clazz);
071: // Append all "legitimate" public read/write fields declared in this class
072: // to the parseSpec.
073: for (int i = 0; i < methodAnalyser.allgetters.length; ++i) {
074: SAXAccessMethod method = methodAnalyser.allgetters[i];
075: if (method.canGet()
076: && method.canSet()
077: && mappingcontext.saxleafparser.isLeafType(method
078: .getAccessedType())
079: && method.getDeclaringClass() == methodAnalyser.targetclass
080: && !method.tagname.equals("anchorField")) {
081: cw.append("," + method.tagname);
082: }
083: }
084: }
085:
086: /**
087: * Create a parsespec string for classes which extend a VP but do not override
088: * the getParseSpec method from SVP. This must only be called for descendents
089: * of SimpleViewParams.
090: *
091: * @param viewparams the viewparams object to make the parse spec string from
092: * @return the complete parse spec string
093: * @author aaronz
094: */
095: private String inferDefaultParseSpec(ViewParameters viewparams) {
096: CharWrap cw = new CharWrap(viewparams.getParseSpec());
097: Class clazz = viewparams.getClass();
098: while (clazz != SimpleViewParameters.class) {
099: appendDeclaredFields(cw, clazz);
100: clazz = clazz.getSuperclass();
101: }
102: return cw.toString();
103: }
104:
105: /**
106: * Create a VPMI from a set of ViewParameters
107: *
108: * @param viewparams the VP object
109: * @return a ViewParamsMapInfo representing the VP object fields
110: */
111: private ConcreteViewParamsMapInfo computeMappingInfo(
112: ViewParameters viewparams) {
113: StringList attnames = new StringList();
114: StringList paths = new StringList();
115: StringList trunkpaths = new StringList();
116:
117: // added this so we can avoid boilerplate code in viewparams -AZ
118: MethodAnalyser ma = mappingcontext.getAnalyser(viewparams
119: .getClass());
120: AccessMethod parsespecaccess = ma.getAccessMethod("parseSpec");
121: String spec = "";
122: if (parsespecaccess.getDeclaringClass() == SimpleViewParameters.class) {
123: // we are using the parseSpec from SVP so no getParseSpec is declared,
124: // now we generate the string it would have generated by default
125: spec = inferDefaultParseSpec(viewparams);
126: } else {
127: // getParseSpec is overridden in the VP so just call it
128: spec = viewparams.getParseSpec();
129: }
130:
131: String[] fields = spec.split(",");
132: for (int i = 0; i < fields.length; ++i) {
133: String field = fields[i].trim();
134: int colpos = field.indexOf(':');
135: if (colpos == -1) {
136: // specification without a colon has attribute name agreeing with field
137: // name
138: attnames.add(field);
139: paths.add(field);
140: } else {
141: String attrname = field.substring(0, colpos);
142: String path = field.substring(colpos + 1);
143: int trunkindex = -1;
144: if (attrname
145: .startsWith(ViewParameters.TRUNK_PARSE_PREFIX)) {
146: try {
147: trunkindex = Integer
148: .parseInt(attrname
149: .substring(ViewParameters.TRUNK_PARSE_PREFIX
150: .length()));
151: if (trunkindex < 0)
152: throw new IllegalArgumentException(
153: "Trunk index must be positive");
154: } catch (Exception e) {
155: throw UniversalRuntimeException
156: .accumulate(
157: e,
158: "Invalid parse specification, "
159: + attrname
160: + " does not contain a trunk index");
161: }
162: ListUtil.setSafe(trunkpaths, trunkindex, path);
163: } else {
164: if (path.endsWith(".*")) {
165: appendLeaves(viewparams.getClass(), path
166: .substring(0, path.length() - 2),
167: attrname, attnames, paths);
168: } else {
169: attnames.add(attrname);
170: paths.add(path);
171: }
172: }
173: }
174: }
175: ConcreteViewParamsMapInfo togo = new ConcreteViewParamsMapInfo();
176: togo.attrnames = attnames.toStringArray();
177: togo.paths = paths.toStringArray();
178: togo.trunkpaths = trunkpaths.toStringArray();
179: for (int i = 0; i < togo.attrnames.length; ++i) {
180: togo.pathToAttr.put(togo.paths[i], togo.attrnames[i]);
181: togo.attrToPath.put(togo.attrnames[i], togo.paths[i]);
182: }
183: for (int i = 0; i < togo.trunkpaths.length; ++i) {
184: if (togo.trunkpaths[i] == null
185: && togo.trunkpaths[i].length() == 0) {
186: throw new IllegalArgumentException(
187: "Error in parseSpec - trunk path at index " + i
188: + " has not been set");
189: }
190: String highattr = ViewParamUtil.getAttrIndex(i, true);
191: String lowattr = ViewParamUtil.getAttrIndex(i, false);
192: togo.pathToAttr.put(togo.trunkpaths[i], highattr);
193: togo.attrToPath.put(highattr, togo.trunkpaths[i]);
194: togo.attrToPath.put(lowattr, togo.trunkpaths[i]);
195: }
196: return togo;
197: }
198:
199: /**
200: * Takes a path ending with a greedy expression (probably .*) and appends the
201: * "leaves" (fields) based on passed in arguments
202: */
203: private void appendLeaves(Class rootclass, String pathroot,
204: String attrprefix, StringList attnames, StringList paths) {
205: String[] components = PathUtil.splitPath(pathroot);
206:
207: Class moveclass = rootclass;
208: for (int i = 0; i < components.length; ++i) {
209: MethodAnalyser ma = mappingcontext.getAnalyser(moveclass);
210: String component = components[i];
211: AccessMethod method = ma.getAccessMethod(component);
212: if (method == null || !(method.canGet() && method.canSet())) {
213: throw new IllegalArgumentException(
214: "Unable to find writeable property for path component "
215: + component + " at " + moveclass);
216: }
217: moveclass = method.getAccessedType();
218: }
219: MethodAnalyser ma = mappingcontext.getAnalyser(moveclass);
220: for (int i = 0; i < ma.allgetters.length; ++i) {
221: SAXAccessMethod method = ma.allgetters[i];
222: if (method.canGet()
223: && method.canSet()
224: && mappingcontext.saxleafparser.isLeafType(method
225: .getAccessedType())) {
226: paths.add(PathUtil.composePathEncoded(pathroot,
227: method.tagname));
228: attnames.add(attrprefix + method.tagname);
229: }
230: }
231: }
232:
233: }
|