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 */
017package org.apache.xbean.propertyeditor;
018
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.List;
025
026/**
027 * Of the javax and java packages in the Java 8 JVM, there are roughly
028 * 10 static factory patterns in use.
029 *
030 * Here they are listed in the order they are preferred by this library
031 *
032 *   64 valueOf
033 *    7 new
034 *    6 decode
035 *    5 for
036 *    4 of
037 *    1 parse
038 *    1 from
039 *    1 create
040 *    1 compile
041 *   40 get
042 *
043 * Though get* has the second most usage in the JVM, it is also the least
044 * consistent and in classes that have multiple factories, it is the least
045 * preferred.
046 *
047 * For each of these prefixes there is a sub order of preference, using
048 * "create" as an example, this is the preferred usage:
049 *
050 *  - create
051 *  - create<Type>
052 *  - create*
053 *
054 */
055public class StaticFactoryConverter extends AbstractConverter {
056
057    private final Method method;
058
059    public StaticFactoryConverter(final Class type, final Method method) {
060        super(type);
061        this.method = method;
062    }
063
064    @Override
065    protected Object toObjectImpl(final String text) {
066        try {
067            return method.invoke(null, text);
068        } catch (final Exception e) {
069            final String message = String.format("Cannot convert string '%s' to %s.", text, super.getType());
070            throw new PropertyEditorException(message, e);
071        }
072    }
073
074    public static StaticFactoryConverter editor(final Class type) {
075        final List<Method> candidates = getCandidates(type);
076
077        if (candidates.size() == 0) return null;
078
079        final Method method = select(candidates);
080
081        return new StaticFactoryConverter(type, method);
082    }
083
084    static List<Method> getCandidates(final Class type) {
085        final List<Method> candidates = new ArrayList<Method>();
086
087        for (final Method method : type.getMethods()) {
088            if (!Modifier.isStatic(method.getModifiers())) continue;
089            if (!Modifier.isPublic(method.getModifiers())) continue;
090            if (!method.getReturnType().equals(type)) continue;
091            if (method.getParameterTypes().length != 1) continue;
092            if (!method.getParameterTypes()[0].equals(String.class)) continue;
093
094            candidates.add(method);
095        }
096
097        return candidates;
098    }
099
100    /**
101     * We want the selection to be stable and not dependent on
102     * VM reflection ordering.
103     */
104    static Method select(final List<Method> candidates) {
105        sort(candidates);
106
107        return candidates.get(0);
108    }
109
110    static void sort(final List<Method> candidates) {
111        Collections.sort(candidates, new Comparator<Method>() {
112            public int compare(final Method a, final Method b) {
113                int av = grade(a);
114                int bv = grade(b);
115                return (a.getName().compareTo(b.getName()) + (av - bv));
116            }
117        });
118    }
119
120    private static int grade(final Method a) {
121        final String type = a.getReturnType().getSimpleName();
122        final String name = a.getName();
123
124        // valueOf beats all
125        if (name.equals("valueOf"))        return -990000;
126        if (name.equals("valueOf" + type)) return -980000;
127        if (name.startsWith("valueOf"))    return -970000;
128
129        // new*
130        if (name.equals("new" + type))    return -890000;
131        if (name.equals("newInstance"))   return -880000;
132        if (name.startsWith("new"))       return -870000;
133
134        // decode*
135        if (name.equals("decode"))        return -790000;
136        if (name.equals("decode" + type)) return -780000;
137        if (name.startsWith("decode"))    return -770000;
138
139        // for*
140        if (name.equals("for" + type))    return -690000;
141        if (name.startsWith("for"))       return -680000;
142
143        // of*
144        if (name.equals("of"))            return -590000;
145        if (name.equals("of" + type))     return -580000;
146        if (name.startsWith("of"))        return -570000;
147
148        // parse*
149        if (name.equals("parse"))         return -490000;
150        if (name.equals("parse" + type))  return -480000;
151        if (name.startsWith("parse"))     return -470000;
152
153        // from*
154        if (name.equals("from"))          return -390000;
155        if (name.equals("fromString"))    return -380000;
156        if (name.startsWith("from"))      return -370000;
157
158        // create*
159        if (name.equals("create"))        return -290000;
160        if (name.equals("create" + type)) return -280000;
161        if (name.startsWith("create"))    return -270000;
162
163        // compile*
164        if (name.equals("compile"))       return -190000;
165        if (name.equals("compile" + type))return -180000;
166        if (name.startsWith("compile"))   return -170000;
167
168        // get*
169        if (name.equals("get"))           return 1000;
170        if (name.equals("get" + type))    return 1200;
171        if (name.equals("getInstance"))   return 1200;
172        if (name.startsWith("get"))       return 1300;
173
174        return 0;
175    }
176}