001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.injection;
018:
019: import java.lang.reflect.Field;
020: import java.util.ArrayList;
021: import java.util.List;
022:
023: import org.apache.wicket.Component;
024: import org.apache.wicket.MarkupContainer;
025: import org.apache.wicket.Page;
026: import org.apache.wicket.markup.html.WebPage;
027: import org.apache.wicket.markup.html.panel.Panel;
028: import org.apache.wicket.util.concurrent.ConcurrentHashMap;
029:
030: /**
031: * Injector scans fields of an object instance and checks if the specified
032: * {@link IFieldValueFactory} can provide a value for a field; if it can, the
033: * field is set to that value. Injector will ignore all non-null fields.
034: *
035: * @author Igor Vaynberg (ivaynberg)
036: *
037: */
038: public class Injector {
039: private static Injector instance = new Injector();
040:
041: // FIXME: WICKET-625 - Wicket doesn't clean up properly when hot-deploying; hangs onto Class references.
042: // We need some way to clean out this hashmap when we're done.
043: private ConcurrentHashMap/* <Class, Field[]> */classToFields = new ConcurrentHashMap();
044:
045: /**
046: * @return static instance of ProxyInjector
047: */
048: public static Injector getInstance() {
049: return instance;
050: }
051:
052: /**
053: * When the initializer traverses the hierarchy of the specified object it
054: * will stop if it encounters a boundary class.
055: *
056: * By default, more common wicket classes are defined as boundaries so that
057: * the initializer does not waste time traversing them.
058: *
059: * @param clazz
060: * class to be tested for being a boundary class
061: * @return true if the class is a boundary class, false otherwise
062: */
063: protected boolean isBoundaryClass(Class clazz) {
064: if (clazz.equals(WebPage.class) || clazz.equals(Page.class)
065: || clazz.equals(Panel.class)
066: || clazz.equals(MarkupContainer.class)
067: || clazz.equals(Component.class)) {
068: return true;
069: }
070: return false;
071: }
072:
073: /**
074: * traverse fields in the class hierarchy of the object and set their value
075: * with a locator provided by the locator factory.
076: *
077: * @param object
078: * @param factory
079: * @return Object that was injected - used for chaining
080: */
081: public Object inject(Object object, IFieldValueFactory factory) {
082: Class clazz = object.getClass();
083: Field[] fields = (Field[]) classToFields.get(clazz);
084: if (fields == null) {
085: fields = findFields(clazz, factory);
086: classToFields.put(clazz, fields);
087: }
088:
089: for (int i = 0; i < fields.length; i++) {
090: final Field field = fields[i];
091:
092: if (!field.isAccessible()) {
093: field.setAccessible(true);
094: }
095: try {
096:
097: if (field.get(object) == null) {
098:
099: Object value = factory.getFieldValue(field, object);
100:
101: if (value != null) {
102: field.set(object, value);
103: }
104: }
105: } catch (IllegalArgumentException e) {
106: throw new RuntimeException(
107: "error while injecting object ["
108: + object.toString() + "] of type ["
109: + object.getClass().getName() + "]", e);
110: } catch (IllegalAccessException e) {
111: throw new RuntimeException(
112: "error while injecting object ["
113: + object.toString() + "] of type ["
114: + object.getClass().getName() + "]", e);
115: }
116: }
117:
118: return object;
119: }
120:
121: /**
122: * Returns an array of fields that can be injected using the given field
123: * value factory
124: *
125: * @param clazz
126: * @param factory
127: * @return an array of fields that can be injected using the given field
128: * value factory
129: */
130: private Field[] findFields(Class clazz, IFieldValueFactory factory) {
131: List/* <Field> */matched = new ArrayList();
132:
133: while (clazz != null && !isBoundaryClass(clazz)) {
134: Field[] fields = clazz.getDeclaredFields();
135: for (int i = 0; i < fields.length; i++) {
136: final Field field = fields[i];
137:
138: if (factory.supportsField(field)) {
139: matched.add(field);
140: }
141: }
142: clazz = clazz.getSuperclass();
143: }
144:
145: return (Field[]) matched.toArray(new Field[matched.size()]);
146: }
147:
148: }
|