001/*
002 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.toolkit;
027
028import java.util.regex.Pattern;
029import javax.lang.model.element.ExecutableElement;
030import javax.lang.model.type.TypeKind;
031import javax.lang.model.type.TypeMirror;
032import javax.lang.model.util.Types;
033
034/**
035 * This class provides basic JavaFX property related utility methods.
036 * Refer to the JavaFX conventions in the VisibleMemberTable comments.
037 */
038public class PropertyUtils {
039
040    final TypeMirror jbObservableType;
041
042    final Pattern fxMethodPatterns;
043
044    final boolean javafx;
045
046    final Types typeUtils;
047
048    PropertyUtils(BaseConfiguration configuration) {
049        BaseOptions options = configuration.getOptions();
050        javafx = options.javafx();
051
052        typeUtils = configuration.docEnv.getTypeUtils();
053
054        // Disable strict check for JDK's without FX.
055        TypeMirror jboType = options.disableJavaFxStrictChecks()
056            ? null
057            : configuration.utils.getSymbol("javafx.beans.Observable");
058
059        jbObservableType = jboType != null
060            ? configuration.docEnv.getTypeUtils().erasure(jboType)
061            : null;
062
063        fxMethodPatterns = javafx
064            ? Pattern.compile("[sg]et\\p{Upper}.*||is\\p{Upper}.*")
065            : null;
066    }
067
068    /**
069     * Returns a base name for a property method. Supposing we
070     * have {@code BooleanProperty acmeProperty()}, then "acme"
071     * will be returned.
072     * @param propertyMethod
073     * @return the base name of a property method.
074     */
075    public String getBaseName(ExecutableElement propertyMethod) {
076        String name = propertyMethod.getSimpleName().toString();
077        String baseName = name.substring(0, name.indexOf("Property"));
078        return baseName;
079    }
080
081    /**
082     * Returns a property getter's name. Supposing we have a property
083     * method {@code DoubleProperty acmeProperty()}, then "getAcme"
084     * will be returned.
085     * @param propertyMethod
086     * @return the property getter's name.
087     */
088    public String getGetName(ExecutableElement propertyMethod) {
089        String baseName = getBaseName(propertyMethod);
090        String fnUppercased = "" +
091            Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
092        return "get" + fnUppercased;
093    }
094
095    /**
096     * Returns an "is" method's name for a property method. Supposing
097     * we have a property method {@code BooleanProperty acmeProperty()},
098     * then "isAcme" will be returned.
099     * @param propertyMethod
100     * @return the property is getter's name.
101     */
102    public String getIsName(ExecutableElement propertyMethod) {
103        String baseName = getBaseName(propertyMethod);
104        String fnUppercased = "" +
105            Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
106        return "is" + fnUppercased;
107    }
108
109    /**
110     * Returns true if a property method could have an "is" method, meaning
111     * {@code isAcme} could exist for a property method.
112     * @param propertyMethod
113     * @return true if the property could have an "is" method, false otherwise.
114     */
115    public boolean hasIsMethod(ExecutableElement propertyMethod) {
116        String propertyTypeName = propertyMethod.getReturnType().toString();
117        return "boolean".equals(propertyTypeName) ||
118            propertyTypeName.endsWith("BooleanProperty");
119    }
120
121    /**
122     * Returns a property setter's name. Supposing we have a property
123     * method {@code DoubleProperty acmeProperty()}, then "setAcme"
124     * will be returned.
125     * @param propertyMethod
126     * @return the property setter's method name.
127     */
128    public String getSetName(ExecutableElement propertyMethod) {
129        String baseName = getBaseName(propertyMethod);
130        String fnUppercased = "" +
131            Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
132        return "set" + fnUppercased;
133    }
134
135    /**
136     * Returns true if the given setter method is a valid property setter
137     * method.
138     * @param setterMethod
139     * @return true if setter method, false otherwise.
140     */
141    public boolean isValidSetterMethod(ExecutableElement setterMethod) {
142        return setterMethod.getReturnType().getKind() == TypeKind.VOID;
143    }
144
145    /**
146     * Returns true if the method is a property method.
147     * @param propertyMethod
148     * @return true if the method is a property method, false otherwise.
149     */
150    public boolean isPropertyMethod(ExecutableElement propertyMethod) {
151        if (!javafx ||
152            !propertyMethod.getParameters().isEmpty() ||
153            !propertyMethod.getTypeParameters().isEmpty()) {
154            return false;
155        }
156        String methodName = propertyMethod.getSimpleName().toString();
157        if (!methodName.endsWith("Property") ||
158            fxMethodPatterns.matcher(methodName).matches()) {
159            return false;
160        }
161
162        TypeMirror returnType = propertyMethod.getReturnType();
163        if (jbObservableType == null) {
164            // JavaFX references missing, make a lazy backward compatible check.
165            return returnType.getKind() != TypeKind.VOID;
166        } else {
167            // Apply strict checks since JavaFX references are available
168            returnType = typeUtils.erasure(propertyMethod.getReturnType());
169            return typeUtils.isAssignable(returnType, jbObservableType);
170        }
171    }
172}