001/*
002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon)
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.golo.runtime;
011
012import gololang.Union;
013import java.lang.invoke.MethodHandle;
014import java.lang.reflect.Method;
015import java.util.Objects;
016
017import static java.lang.invoke.MethodHandles.*;
018import static java.lang.invoke.MethodType.methodType;
019
020public class PropertyMethodFinder extends MethodFinder {
021
022  private static final MethodHandle FLUENT_SETTER;
023
024  static {
025    try {
026      FLUENT_SETTER = lookup().findStatic(
027          PropertyMethodFinder.class,
028          "fluentSetter",
029          methodType(Object.class, Object.class, Object.class));
030    } catch (NoSuchMethodException | IllegalAccessException e) {
031      throw new Error("Could not bootstrap the required fluent method handles", e);
032    }
033  }
034
035  private static Object fluentSetter(Object o, Object notUsedSetterReturnValue) {
036    return o;
037  }
038
039  private String propertyName;
040
041  public PropertyMethodFinder(MethodInvocation invocation, Lookup lookup) {
042    super(invocation, lookup);
043    this.propertyName = capitalize(invocation.name());
044  }
045
046  private MethodHandle findMethodForGetter() {
047    if (Union.class.isAssignableFrom(invocation.receiverClass())) {
048      return null;
049    }
050    MethodHandle target = new RegularMethodFinder(
051        invocation.withName("get" + propertyName),
052        lookup
053    ).find();
054
055    if (target != null) {
056      return target;
057    }
058    return new RegularMethodFinder(
059        invocation.withName("is" + propertyName),
060        lookup
061    ).find();
062  }
063
064  private MethodHandle fluentMethodHandle(Method candidate) {
065    Objects.requireNonNull(candidate);
066    MethodHandle target = toMethodHandle(candidate).orElse(null);
067    if (target != null) {
068      if (!TypeMatching.returnsValue(candidate)) {
069        Object receiver = invocation.arguments()[0];
070        MethodHandle fluent = FLUENT_SETTER.bindTo(receiver);
071        target = filterReturnValue(target, fluent);
072      }
073    }
074    return target;
075  }
076
077  private MethodHandle findMethodForSetter() {
078    return new RegularMethodFinder(
079        invocation.withName("set" + propertyName),
080        lookup)
081        .findInMethods()
082        .filter(method -> !Union.class.isAssignableFrom(method.getDeclaringClass()))
083        .map(this::fluentMethodHandle)
084        .findFirst()
085        .orElse(null);
086  }
087
088  @Override
089  public MethodHandle find() {
090    if (invocation.arity() == 1) {
091      return findMethodForGetter();
092    }
093    return findMethodForSetter();
094  }
095
096  private static String capitalize(String word) {
097    return Character.toUpperCase(word.charAt(0)) + word.substring(1);
098  }
099}