| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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.apache.tools.ant.filters.util; |
| |
| import java.io.FilterReader; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Vector; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import org.apache.tools.ant.AntClassLoader; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.filters.BaseFilterReader; |
| import org.apache.tools.ant.filters.ChainableReader; |
| import org.apache.tools.ant.types.AntFilterReader; |
| import org.apache.tools.ant.types.FilterChain; |
| import org.apache.tools.ant.types.Parameterizable; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Process a FilterReader chain. |
| * |
| */ |
| public final class ChainReaderHelper { |
| /** |
| * Created type. |
| */ |
| public class ChainReader extends FilterReader { |
| |
| private List<AntClassLoader> cleanupLoaders; |
| |
| private ChainReader(Reader in, List<AntClassLoader> cleanupLoaders) { |
| super(in); |
| this.cleanupLoaders = cleanupLoaders; |
| } |
| |
| public String readFully() throws IOException { |
| return ChainReaderHelper.this.readFully(this); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| cleanUpClassLoaders(cleanupLoaders); |
| super.close(); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| close(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| } |
| |
| // default buffer size |
| private static final int DEFAULT_BUFFER_SIZE = 8192; |
| // CheckStyle:VisibilityModifier OFF - bc |
| /** |
| * The primary reader to which the reader chain is to be attached. |
| */ |
| public Reader primaryReader; |
| |
| /** |
| * The size of the buffer to be used. |
| */ |
| public int bufferSize = DEFAULT_BUFFER_SIZE; |
| |
| /** |
| * Chain of filters |
| */ |
| public Vector<FilterChain> filterChains = new Vector<>(); |
| |
| /** The Ant project */ |
| private Project project = null; |
| |
| // CheckStyle:VisibilityModifier ON |
| |
| /** |
| * Default constructor. |
| */ |
| public ChainReaderHelper() { |
| } |
| |
| /** |
| * Convenience constructor. |
| * @param project ditto |
| * @param primaryReader ditto |
| * @param filterChains ditto |
| */ |
| public ChainReaderHelper(Project project, Reader primaryReader, |
| Iterable<FilterChain> filterChains) { |
| withProject(project).withPrimaryReader(primaryReader) |
| .withFilterChains(filterChains); |
| } |
| |
| /** |
| * Sets the primary {@link Reader} |
| * @param rdr the reader object |
| */ |
| public void setPrimaryReader(Reader rdr) { |
| primaryReader = rdr; |
| } |
| |
| /** |
| * Fluent primary {@link Reader} mutator. |
| * @param rdr Reader |
| * @return {@code this} |
| */ |
| public ChainReaderHelper withPrimaryReader(Reader rdr) { |
| setPrimaryReader(rdr); |
| return this; |
| } |
| |
| /** |
| * Set the project to work with |
| * @param project the current project |
| */ |
| public void setProject(final Project project) { |
| this.project = project; |
| } |
| |
| /** |
| * Fluent {@link Project} mutator. |
| * @param project ditto |
| * @return {@code this} |
| */ |
| public ChainReaderHelper withProject(Project project) { |
| setProject(project); |
| return this; |
| } |
| |
| /** |
| * Get the project |
| * |
| * @return the current project |
| */ |
| public Project getProject() { |
| return project; |
| } |
| |
| /** |
| * Sets the buffer size to be used. Defaults to 8192, |
| * if this method is not invoked. |
| * @param size the buffer size to use |
| */ |
| public void setBufferSize(int size) { |
| bufferSize = size; |
| } |
| |
| /** |
| * Fluent buffer size mutator. |
| * @param size ditto |
| * @return {@code this} |
| */ |
| public ChainReaderHelper withBufferSize(int size) { |
| setBufferSize(size); |
| return this; |
| } |
| |
| /** |
| * Sets the collection of filter reader sets |
| * |
| * @param fchain the filter chains collection |
| */ |
| public void setFilterChains(Vector<FilterChain> fchain) { |
| filterChains = fchain; |
| } |
| |
| /** |
| * Fluent {@code filterChains} mutator. |
| * @param filterChains ditto |
| * @return {@code this} |
| */ |
| public ChainReaderHelper withFilterChains(Iterable<FilterChain> filterChains) { |
| final Vector<FilterChain> fcs; |
| if (filterChains instanceof Vector<?>) { |
| fcs = (Vector<FilterChain>) filterChains; |
| } else { |
| fcs = new Vector<>(); |
| filterChains.forEach(fcs::add); |
| } |
| setFilterChains(fcs); |
| return this; |
| } |
| |
| /** |
| * Fluent mechanism to apply some {@link Consumer}. |
| * @param consumer ditto |
| * @return {@code this} |
| */ |
| public ChainReaderHelper with(Consumer<ChainReaderHelper> consumer) { |
| consumer.accept(this); |
| return this; |
| } |
| |
| /** |
| * Assemble the reader |
| * @return the assembled reader |
| * @exception BuildException if an error occurs |
| */ |
| public ChainReader getAssembledReader() throws BuildException { |
| if (primaryReader == null) { |
| throw new BuildException("primaryReader must not be null."); |
| } |
| |
| Reader instream = primaryReader; |
| final List<AntClassLoader> classLoadersToCleanUp = new ArrayList<>(); |
| |
| final List<Object> finalFilters = |
| filterChains.stream().map(FilterChain::getFilterReaders) |
| .flatMap(Collection::stream).collect(Collectors.toList()); |
| |
| if (!finalFilters.isEmpty()) { |
| boolean success = false; |
| try { |
| for (Object o : finalFilters) { |
| if (o instanceof AntFilterReader) { |
| instream = |
| expandReader((AntFilterReader) o, |
| instream, classLoadersToCleanUp); |
| } else if (o instanceof ChainableReader) { |
| setProjectOnObject(o); |
| instream = ((ChainableReader) o).chain(instream); |
| setProjectOnObject(instream); |
| } |
| } |
| success = true; |
| } finally { |
| if (!success && !classLoadersToCleanUp.isEmpty()) { |
| cleanUpClassLoaders(classLoadersToCleanUp); |
| } |
| } |
| } |
| return new ChainReader(instream, classLoadersToCleanUp); |
| } |
| |
| /** |
| * helper method to set the project on an object. |
| * the reflection setProject does not work for anonymous/protected/private |
| * classes, even if they have public methods. |
| */ |
| private void setProjectOnObject(Object obj) { |
| if (project == null) { |
| return; |
| } |
| if (obj instanceof BaseFilterReader) { |
| ((BaseFilterReader) obj).setProject(project); |
| return; |
| } |
| project.setProjectReference(obj); |
| } |
| |
| /** |
| * Deregisters Classloaders from the project so GC can remove them later. |
| */ |
| private static void cleanUpClassLoaders(List<AntClassLoader> loaders) { |
| loaders.forEach(AntClassLoader::cleanup); |
| } |
| |
| /** |
| * Read data from the reader and return the |
| * contents as a string. |
| * @param rdr the reader object |
| * @return the contents of the file as a string |
| * @exception IOException if an error occurs |
| */ |
| public String readFully(Reader rdr) throws IOException { |
| return FileUtils.readFully(rdr, bufferSize); |
| } |
| |
| /** |
| * Creates and parameterizes a new FilterReader from a |
| * <filterreader> element. |
| * |
| * @since Ant 1.8.0 |
| */ |
| private Reader expandReader(final AntFilterReader filter, |
| final Reader ancestor, |
| final List<AntClassLoader> classLoadersToCleanUp) { |
| final String className = filter.getClassName(); |
| final Path classpath = filter.getClasspath(); |
| if (className != null) { |
| try { |
| Class<? extends FilterReader> clazz; |
| try { |
| if (classpath == null) { |
| clazz = Class.forName(className) |
| .asSubclass(FilterReader.class); |
| } else { |
| AntClassLoader al = |
| filter.getProject().createClassLoader(classpath); |
| classLoadersToCleanUp.add(al); |
| clazz = Class.forName(className, true, al) |
| .asSubclass(FilterReader.class); |
| } |
| } catch (ClassCastException ex) { |
| throw new BuildException("%s does not extend %s", className, |
| FilterReader.class.getName()); |
| } |
| Optional<Constructor<?>> ctor = |
| Stream.of(clazz.getConstructors()) |
| .filter(c -> c.getParameterCount() == 1 |
| && c.getParameterTypes()[0] |
| .isAssignableFrom(Reader.class)) |
| .findFirst(); |
| |
| Object instream = ctor |
| .orElseThrow(() -> new BuildException( |
| "%s does not define a public constructor that takes in a %s as its single argument.", |
| className, Reader.class.getSimpleName())) |
| .newInstance(ancestor); |
| |
| setProjectOnObject(instream); |
| if (Parameterizable.class.isAssignableFrom(clazz)) { |
| ((Parameterizable) instream).setParameters(filter.getParams()); |
| } |
| return (Reader) instream; |
| } catch (ClassNotFoundException | InstantiationException |
| | IllegalAccessException | InvocationTargetException ex) { |
| throw new BuildException(ex); |
| } |
| } |
| // Ant 1.7.1 and earlier ignore <filterreader> without a |
| // classname attribute, not sure this is a good idea - |
| // backwards compatibility makes it hard to change, though. |
| return ancestor; |
| } |
| } |