/*
 *  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.
/** * Process a FilterReader chain. * */
public final class ChainReaderHelper {
Created type.
/** * 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.
/** * The primary reader to which the reader chain is to be attached. */
public Reader primaryReader;
The size of the buffer to be used.
/** * The size of the buffer to be used. */
public int bufferSize = DEFAULT_BUFFER_SIZE;
Chain of filters
/** * Chain of filters */
public Vector<FilterChain> filterChains = new Vector<>();
The Ant project
/** The Ant project */
private Project project = null; // CheckStyle:VisibilityModifier ON
Default constructor.
/** * Default constructor. */
public ChainReaderHelper() { }
Convenience constructor.
Params:
  • project – ditto
  • primaryReader – ditto
  • filterChains – ditto
/** * 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 Reader
Params:
  • rdr – the reader object
/** * Sets the primary {@link Reader} * @param rdr the reader object */
public void setPrimaryReader(Reader rdr) { primaryReader = rdr; }
Fluent primary Reader mutator.
Params:
  • rdr – Reader
Returns:this
/** * 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
Params:
  • project – the current project
/** * Set the project to work with * @param project the current project */
public void setProject(final Project project) { this.project = project; }
Fluent Project mutator.
Params:
  • project – ditto
Returns:this
/** * Fluent {@link Project} mutator. * @param project ditto * @return {@code this} */
public ChainReaderHelper withProject(Project project) { setProject(project); return this; }
Get the project
Returns:the current project
/** * 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.
Params:
  • size – the buffer size to use
/** * 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.
Params:
  • size – ditto
Returns:this
/** * 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
Params:
  • fchain – the filter chains collection
/** * Sets the collection of filter reader sets * * @param fchain the filter chains collection */
public void setFilterChains(Vector<FilterChain> fchain) { filterChains = fchain; }
Fluent filterChains mutator.
Params:
  • filterChains – ditto
Returns:this
/** * 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 Consumer.
Params:
  • consumer – ditto
Returns: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
Throws:
Returns:the assembled reader
/** * 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.
/** * 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.
/** * 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.
Params:
  • rdr – the reader object
Throws:
Returns:the contents of the file as a string
/** * 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
/** * Creates and parameterizes a new FilterReader from a * &lt;filterreader&gt; 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; } }