All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.joda.beans.PropertyPath Maven / Gradle / Ivy

There is a newer version: 2.11.1
Show newest version
/*
 *  Copyright 2001-present Stephen Colebourne
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.joda.beans;

import static java.util.stream.Collectors.toList;
import static org.joda.beans.JodaBeanUtils.notNull;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * A multi-stage property path.
 * 

* This accepts a dot-separated path and queries the bean. * Each dot-separated part of the path is resolved to a meta-property. * Thus the path "foo.bar.baz" is equivalent to {@code bean.getFoo().getBar().getBaz()}. * The path lookup works even if the methods are not public. *

* Each part of the path may contain a suffix, such as {@code []} or {@code []}. * The suffix {@code []} accesses the specified numeric index of an {@code Iterable}. * The suffix {@code []} accesses the specified numeric index of an {@code Map}. * * @param

the type of the result * @since 2.11.0 */ public final class PropertyPath

{ /** * The path entries. */ private final String propertyPath; /** * The result type. */ private final Class

resultType; /** * The path entries. */ private final List pathEntries; /** * Restricted constructor. */ private PropertyPath(String propertyPath, Class

resultType, List pathEntries) { this.propertyPath = propertyPath; this.resultType = resultType; this.pathEntries = pathEntries; } //------------------------------------------------------------------------- /** * Obtains an instance from the path. * * @param

the type of the result * @param propertyPath the path, not null * @param resultType the result type, not null * @return the path * @throws IllegalArgumentException if the path has an invalid format */ public static

PropertyPath

of(String propertyPath, Class

resultType) { notNull(propertyPath, "propertyPath"); notNull(resultType, "resultType"); List split = PathEntry.parse(propertyPath); return new PropertyPath<>(propertyPath, resultType, split); } //------------------------------------------------------------------------- /** * Gets a value by path from the specified bean. *

* This uses the path to query the bean. * There is special handling for {@code Iterable}, {@code Map} and {@code Optional}. * If the path does not match the structure within the bean, optional empty is returned. * If the path finds any nulls, empty lists or empty maps, optional empty is returned. * * @param bean the bean to start from, not null * @return the value, empty if the value is null or the path fails to evaluate correctly */ public Optional

get(Bean bean) { notNull(bean, "bean"); Bean currentBean = bean; for (int i = 0; i < pathEntries.size() - 1; i++) { PathEntry pathEntry = pathEntries.get(i); Object obj = pathEntry.get(currentBean); obj = pathEntry.extract(obj); if (obj instanceof Optional) { obj = ((Optional) obj).orElse(null); } if (obj == null) { return Optional.empty(); } if (!(obj instanceof Bean)) { return Optional.empty(); } currentBean = (Bean) obj; } // last entry, which allows for possibility that resultType = Optional.class PathEntry pathEntry = pathEntries.get(pathEntries.size() - 1); Object obj = pathEntry.get(currentBean); obj = pathEntry.extract(obj); if (obj == null) { return Optional.empty(); } if (resultType.isInstance(obj)) { return Optional.of(resultType.cast(obj)); } else { if (obj instanceof Optional) { obj = ((Optional) obj).orElse(null); } if (resultType.isInstance(obj)) { return Optional.of(resultType.cast(obj)); } return Optional.empty(); } } //------------------------------------------------------------------------- /** * Gets the property path. * * @return the property path */ public String propertyPath() { return propertyPath; } /** * Gets the result type. * * @return the result type */ public Class

resultType() { return resultType; } @Override public boolean equals(Object obj) { if (obj instanceof PropertyPath) { PropertyPath other = (PropertyPath) obj; return this.propertyPath.equals(other.propertyPath) && this.resultType.equals(other.resultType); } return false; } @Override public int hashCode() { return propertyPath.hashCode() ^ resultType.hashCode(); } @Override public String toString() { return propertyPath + ": " + resultType.getName(); } //------------------------------------------------------------------------- private static final class PathEntry { private final String propertyName; private final String key; private final int index; static List parse(String propertyPath) { String[] split = propertyPath.split("\\."); return Stream.of(split) .map(entryStr -> extractEntry(propertyPath, entryStr)) .collect(toList()); } private static PathEntry extractEntry(String propertyPath, String entryStr) { String propName = entryStr; String key = null; int index = 0; int start = entryStr.lastIndexOf('['); if (entryStr.endsWith("]") && start > 0) { key = entryStr.substring(start + 1, entryStr.length() - 1); if (key.length() == 0) { throw new IllegalArgumentException("Invalid property path, empty key: " + propertyPath); } char firstChar = key.charAt(0); index = -1; if (firstChar == '-' || (firstChar >= '0' && firstChar <= '9')) { try { index = Integer.parseInt(key); } catch (NumberFormatException ex) { // index = -1 } } propName = entryStr.substring(0, start); } return new PathEntry(propName, key, index); } private PathEntry(String propertyName, String key, int index) { this.propertyName = propertyName; this.key = key; this.index = index; } private Object get(Bean bean) { try { return bean.metaBean().metaProperty(propertyName).get(bean); } catch (RuntimeException ex) { return null; } } private Object extract(Object obj) { // maps can be queried using the [key] suffix if desired // an [index] suffix will be queried as a key, not an index if (obj instanceof Map) { if (key == null) { return extract(((Map) obj).values()); } else { Map map = ((Map) obj); for (Entry mapEntry : map.entrySet()) { if (key.equals(mapEntry.getKey())) { return mapEntry.getValue(); } } return null; } } // lists/sets can be queried using the [index] suffix if desired if (obj instanceof Iterable) { if (key != null && index < 0) { return null; } if (obj instanceof List) { List list = (List) obj; if (index < list.size()) { return list.get(index); } return null; } Iterator it = ((Iterable) obj).iterator(); int i = 0; while (it.hasNext() && i < index) { it.next(); i++; } return it.hasNext() ? it.next() : null; } // not a collection if (key != null && !"0".equals(key)) { return null; } return obj; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy