栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

FreeMarker源码分析(四)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

FreeMarker源码分析(四)

2021SC@SDUSC

目录

template包

Template.java

源码展示

关键代码分析

template()构造方法

getPlainTextTemplate()方法

 createProcessingEnvironment()方法

getPrefixedName()方法

containingElements()方法


template包

与log有关的代码分析已经完成接下来集中于对FreeMarker的template进行分析。

模板引擎的作用

  • 模板引擎的目标是“数据+模板=结果”
  • 模板引擎将数据与展现有效的“解耦”
  • 前端只需要知道怎么编写前端,后端只需关注后端,用模板引擎把两者整合

Template.java

源码展示

package freemarker.template;

import java.io.BufferedReader;
import java.io.FilterReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import javax.swing.tree.TreePath;

import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateLoader;
import freemarker.cache.TemplateLookupStrategy;
import freemarker.core.BugException;
import freemarker.core.Configurable;
import freemarker.core.Environment;
import freemarker.core.FMParser;
import freemarker.core.LibraryLoad;
import freemarker.core.Macro;
import freemarker.core.OutputFormat;
import freemarker.core.ParseException;
import freemarker.core.ParserConfiguration;
import freemarker.core.TemplateConfiguration;
import freemarker.core.TemplateElement;
import freemarker.core.TextBlock;
import freemarker.core.TokenMgrError;
import freemarker.core._CoreAPI;
import freemarker.debug.impl.DebuggerService;


public class Template extends Configurable {
    public static final String DEFAULT_NAMESPACE_PREFIX = "D";
    public static final String NO_NS_PREFIX = "N";

    private static final int READER_BUFFER_SIZE = 4096;
    
    private Map macros = new HashMap();
    private List imports = new Vector();
    private TemplateElement rootElement;
    private String encoding, defaultNS;
    private Object customLookupCondition;
    private int interpolationSyntax;
    private int actualTagSyntax;
    private int actualNamingConvention;
    private boolean autoEscaping;
    private OutputFormat outputFormat;
    private final String name;
    private final String sourceName;
    private final ArrayList lines = new ArrayList();
    private final ParserConfiguration parserConfiguration;
    private Map prefixTonamespaceURILookup = new HashMap();
    private Map namespaceURIToPrefixLookup = new HashMap();
    private Version templateLanguageVersion;

    
    private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) {
        super(tononNull(cfg));
        this.name = name;
        this.sourceName = sourceName;
        this.templateLanguageVersion = normalizeTemplateLanguageVersion(tononNull(cfg).getIncompatibleImprovements());
        this.parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration();
    }

    private static Configuration toNonNull(Configuration cfg) {
        return cfg != null ? cfg : Configuration.getDefaultConfiguration();
    }

    
    public Template(String name, Reader reader, Configuration cfg) throws IOException {
        this(name, null, reader, cfg);
    }

    
    public Template(String name, String sourceCode, Configuration cfg) throws IOException {
        this(name, new StringReader(sourceCode), cfg);
    }

    
    public Template(String name, Reader reader, Configuration cfg, String encoding) throws IOException {
        this(name, null, reader, cfg, encoding);
    }

    
   public Template(
           String name, String sourceName, Reader reader, Configuration cfg) throws IOException {
       this(name, sourceName, reader, cfg, null);
   }
    
    
   public Template(
           String name, String sourceName, Reader reader, Configuration cfg, String encoding) throws IOException {
       this(name, sourceName, reader, cfg, null, encoding);
   }
   
    
    public Template(
           String name, String sourceName, Reader reader,
           Configuration cfg, ParserConfiguration customParserConfiguration,
           String encoding) throws IOException {
        this(name, sourceName, cfg, customParserConfiguration);
        
        this.setEncoding(encoding);
        LineTableBuilder ltbReader;
        try {
            ParserConfiguration actualParserConfiguration = getParserConfiguration();
            
            if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) {
                reader = new BufferedReader(reader, READER_BUFFER_SIZE);
            }
            ltbReader = new LineTableBuilder(reader, actualParserConfiguration);
            reader = ltbReader;
            
            try {
                FMParser parser = new FMParser(this, reader, actualParserConfiguration);
                if (cfg != null) {
                    _CoreAPI.setPreventStrippings(parser, cfg.getPreventStrippings());
                }
                try {
                    this.rootElement = parser.Root();
                } catch (IndexOutOfBoundsException exc) {
                    // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails with
                    // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we suppress the
                    // IndexOutOfBoundsException and let the real cause to be thrown later. 
                    if (!ltbReader.hasFailure()) {
                        throw exc;
                    }
                    rootElement = null;
                }
                this.actualTagSyntax = parser._getLastTagSyntax();
                this.interpolationSyntax = actualParserConfiguration.getInterpolationSyntax();
                this.actualNamingConvention = parser._getLastNamingConvention();
            } catch (TokenMgrError exc) {
                // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it
                // to ParseException
                throw exc.toParseException(this);
            }
        } catch (ParseException e) {
            e.setTemplateName(getSourceName());
            throw e;
        } finally {
            reader.close();
        }
        
        // Throws any exception that JavaCC has silently treated as EOF:
        ltbReader.throwFailure();
        
        DebuggerService.registerTemplate(this);
        namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup);
        prefixTonamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup);
    }

    
    @Deprecated
    public Template(String name, Reader reader) throws IOException {
        this(name, reader, null);
    }

    
    @Deprecated
    // [2.4] remove this
    Template(String name, TemplateElement root, Configuration cfg) {
        this(name, null, cfg, (ParserConfiguration) null);
        this.rootElement = root;
        DebuggerService.registerTemplate(this);
    }
    
    
    static public Template getPlainTextTemplate(String name, String content, Configuration config) {
        return getPlainTextTemplate(name, null, content, config);
    }
    
    
    static public Template getPlainTextTemplate(String name, String sourceName, String content, Configuration config) {
        Template template;
        try {
            template = new Template(name, sourceName, new StringReader("X"), config);
        } catch (IOException e) {
            throw new BugException("Plain text template creation failed", e);
        }
        _CoreAPI.replaceText((TextBlock) template.rootElement, content);
        DebuggerService.registerTemplate(template);
        return template;
    }

    private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) {
        _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
        int v = incompatibleImprovements.intValue();
        if (v < _TemplateAPI.VERSION_INT_2_3_19) {
            return Configuration.VERSION_2_3_0;
        } else if (v > _TemplateAPI.VERSION_INT_2_3_21) {
            return Configuration.VERSION_2_3_21;
        } else { // if 2.3.19 or 2.3.20 or 2.3.21
            return incompatibleImprovements;
        }
    }

    
    public void process(Object dataModel, Writer out)
    throws TemplateException, IOException {
        createProcessingEnvironment(dataModel, out, null).process();
    }

    
    public void process(Object dataModel, Writer out, ObjectWrapper wrapper, TemplateNodeModel rootNode)
    throws TemplateException, IOException {
        Environment env = createProcessingEnvironment(dataModel, out, wrapper);
        if (rootNode != null) {
            env.setCurrentVisitorNode(rootNode);
        }
        env.process();
    }
    
    
    public void process(Object dataModel, Writer out, ObjectWrapper wrapper)
    throws TemplateException, IOException {
        createProcessingEnvironment(dataModel, out, wrapper).process();
    }
    
   
    public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper)
    throws TemplateException, IOException {
        final TemplateHashModel dataModelHash;
        if (dataModel instanceof TemplateHashModel) {
            dataModelHash = (TemplateHashModel) dataModel;
        } else {
            if (wrapper == null) {
                wrapper = getObjectWrapper();
            }

            if (dataModel == null) {
                dataModelHash = new SimpleHash(wrapper);
            } else {
                TemplateModel wrappedDataModel = wrapper.wrap(dataModel);
                if (wrappedDataModel instanceof TemplateHashModel) {
                    dataModelHash = (TemplateHashModel) wrappedDataModel;
                } else if (wrappedDataModel == null) {
                    throw new IllegalArgumentException(
                            wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null.");
                } else {
                    throw new IllegalArgumentException(
                            wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName()
                            + " to a TemplateHashModel. Generally, you want to use a Map or a "
                            + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean "
                            + "property names will be the variable names in the template.");
                }
            }
        }
        return new Environment(this, dataModelHash, out);
    }

    
    public Environment createProcessingEnvironment(Object dataModel, Writer out)
    throws TemplateException, IOException {
        return createProcessingEnvironment(dataModel, out, null);
    }
    
    
    @Override
    public String toString() {
        StringWriter sw = new StringWriter();
        try {
            dump(sw);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe.getMessage());
        }
        return sw.toString();
    }


    
    public String getName() {
        return name;
    }

    
    public String getSourceName() {
        return sourceName != null ? sourceName : getName();
    }

    
    public Configuration getConfiguration() {
        return (Configuration) getParent();
    }
    
    
    public ParserConfiguration getParserConfiguration() {
        return parserConfiguration;
    }
    
    
    Version getTemplateLanguageVersion() {
        return templateLanguageVersion;
    }

    
    @Deprecated
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    
    public String getEncoding() {
        return this.encoding;
    }
    
    
    public Object getCustomLookupCondition() {
        return customLookupCondition;
    }

    
    public void setCustomLookupCondition(Object customLookupCondition) {
        this.customLookupCondition = customLookupCondition;
    }

    
    public int getActualTagSyntax() {
        return actualTagSyntax;
    }
    
    
    public int getInterpolationSyntax() {
        return interpolationSyntax;
    }
    
    
    public int getActualNamingConvention() {
        return actualNamingConvention;
    }
    
    
    public OutputFormat getOutputFormat() {
        return outputFormat;
    }
    
    
    void setOutputFormat(OutputFormat outputFormat) {
        this.outputFormat = outputFormat;
    }
    
    
    public boolean getAutoEscaping() {
        return autoEscaping;
    }

    
    void setAutoEscaping(boolean autoEscaping) {
        this.autoEscaping = autoEscaping;
    }
    
    
    public void dump(PrintStream ps) {
        ps.print(rootElement.getCanonicalForm());
    }

    
    public void dump(Writer out) throws IOException {
        out.write(rootElement.getCanonicalForm());
    }

    
    @Deprecated
    public void addMacro(Macro macro) {
        macros.put(macro.getName(), macro);
    }

    
    @Deprecated
    public void addimport(LibraryLoad ll) {
        imports.add(ll);
    }

    
    public String getSource(int beginColumn,
                            int beginLine,
                            int endColumn,
                            int endLine) {
        if (beginLine < 1 || endLine < 1) return null;  // dynamically ?eval-ed expressions has no source available
        
        // Our container is zero-based.
        --beginLine;
        --beginColumn;
        --endColumn;
        --endLine;
        StringBuilder buf = new StringBuilder();
        for (int i = beginLine ; i <= endLine; i++) {
            if (i < lines.size()) {
                buf.append(lines.get(i));
            }
        }
        int lastLineLength = lines.get(endLine).toString().length();
        int trailingCharsToDelete = lastLineLength - endColumn - 1;
        buf.delete(0, beginColumn);
        buf.delete(buf.length() - trailingCharsToDelete, buf.length());
        return buf.toString();
    }

    
    private class LineTableBuilder extends FilterReader {
        
        private final int tabSize;
        private final StringBuilder lineBuf = new StringBuilder();
        int lastChar;
        boolean closed;
        
         
        private Exception failure; 

        
        LineTableBuilder(Reader r, ParserConfiguration parserConfiguration) {
            super(r);
            tabSize = parserConfiguration.getTabSize();
        }
        
        public boolean hasFailure() {
            return failure != null;
        }

        public void throwFailure() throws IOException {
            if (failure != null) {
                if (failure instanceof IOException) {
                    throw (IOException) failure;
                }
                if (failure instanceof RuntimeException) {
                    throw (RuntimeException) failure;
                }
                throw new UndeclaredThrowableException(failure);
            }
        }

        @Override
        public int read() throws IOException {
            try {
                int c = in.read();
                handleChar(c);
                return c;
            } catch (Exception e) {
                throw rememberException(e);
            }
        }

        private IOException rememberException(Exception e) throws IOException {
            // JavaCC used to read from the Reader after it was closed. So we must not treat that as a failure. 
            if (!closed) {
                failure = e;
            }
            if (e instanceof IOException) {
                return (IOException) e;
            }
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new UndeclaredThrowableException(e);
        }

        @Override
        public int read(char cbuf[], int off, int len) throws IOException {
            try {
                int numchars = in.read(cbuf, off, len);
                for (int i = off; i < off + numchars; i++) {
                    char c = cbuf[i];
                    handleChar(c);
                }
                return numchars;
            } catch (Exception e) {
                throw rememberException(e);
            }
        }

        @Override
        public void close() throws IOException {
            if (lineBuf.length() > 0) {
                lines.add(lineBuf.toString());
                lineBuf.setLength(0);
            }
            super.close();
            closed = true;
        }

        private void handleChar(int c) {
            if (c == 'n' || c == 'r') {
                if (lastChar == 'r' && c == 'n') { // CRLF under Windoze
                    int lastIndex = lines.size() - 1;
                    String lastLine = (String) lines.get(lastIndex);
                    lines.set(lastIndex, lastLine + 'n');
                } else {
                    lineBuf.append((char) c);
                    lines.add(lineBuf.toString());
                    lineBuf.setLength(0);
                }
            } else if (c == 't' && tabSize != 1) {
                int numSpaces = tabSize - (lineBuf.length() % tabSize);
                for (int i = 0; i < numSpaces; i++) {
                    lineBuf.append(' ');
                }
            } else {
                lineBuf.append((char) c);
            }
            lastChar = c;
        }
    }

    
    @Deprecated
    public TemplateElement getRootTreeNode() {
        return rootElement;
    }
    
    
    @Deprecated
    public Map getMacros() {
        return macros;
    }

    
    @Deprecated
    public List getimports() {
        return imports;
    }

    
    @Deprecated
    public void addPrefixNSMapping(String prefix, String nsURI) {
        if (nsURI.length() == 0) {
            throw new IllegalArgumentException("Cannot map empty string URI");
        }
        if (prefix.length() == 0) {
            throw new IllegalArgumentException("Cannot map empty string prefix");
        }
        if (prefix.equals(NO_NS_PREFIX)) {
            throw new IllegalArgumentException("The prefix: " + prefix + " cannot be registered, it's reserved for special internal use.");
        }
        if (prefixToNamespaceURILookup.containsKey(prefix)) {
            throw new IllegalArgumentException("The prefix: '" + prefix + "' was repeated. This is illegal.");
        }
        if (namespaceURIToPrefixLookup.containsKey(nsURI)) {
            throw new IllegalArgumentException("The namespace URI: " + nsURI + " cannot be mapped to 2 different prefixes.");
        }
        if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) {
            this.defaultNS = nsURI;
        } else {
            prefixToNamespaceURILookup.put(prefix, nsURI);
            namespaceURIToPrefixLookup.put(nsURI, prefix);
        }
    }
    
    public String getDefaultNS() {
        return this.defaultNS;
    }
    
    
    public String getNamespaceForPrefix(String prefix) {
        if (prefix.equals("")) {
            return defaultNS == null ? "" : defaultNS;
        }
        return (String) prefixToNamespaceURILookup.get(prefix);
    }
    
    
    public String getPrefixForNamespace(String nsURI) {
        if (nsURI == null) {
            return null;
        }
        if (nsURI.length() == 0) {
            return defaultNS == null ? "" : NO_NS_PREFIX;
        }
        if (nsURI.equals(defaultNS)) {
            return "";
        }
        return (String) namespaceURIToPrefixLookup.get(nsURI);
    }
    
    
    public String getPrefixedName(String localName, String nsURI) {
        if (nsURI == null || nsURI.length() == 0) {
            if (defaultNS != null) {
                return NO_NS_PREFIX + ":" + localName;
            } else {
                return localName;
            }
        } 
        if (nsURI.equals(defaultNS)) {
            return localName;
        } 
        String prefix = getPrefixForNamespace(nsURI);
        if (prefix == null) {
            return null;
        }
        return prefix + ":" + localName;
    }
    
    
    @Deprecated
    public TreePath containingElements(int column, int line) {
        final ArrayList elements = new ArrayList();
        TemplateElement element = rootElement;
        mainloop: while (element.contains(column, line)) {
            elements.add(element);
            for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) {
                TemplateElement elem = (TemplateElement) enumeration.nextElement();
                if (elem.contains(column, line)) {
                    element = elem;
                    continue mainloop;
                }
            }
            break;
        }
        if (elements.isEmpty()) {
            return null;
        }
        return new TreePath(elements.toArray());
    }

    
    static public class WrongEncodingException extends ParseException {
        private static final long serialVersionUID = 1L;

        
        @Deprecated
        public String specifiedEncoding;
        
        private final String constructorSpecifiedEncoding;

        
        @Deprecated
        public WrongEncodingException(String templateSpecifiedEncoding) {
            this(templateSpecifiedEncoding, null);
        }

        
        public WrongEncodingException(String templateSpecifiedEncoding, String constructorSpecifiedEncoding) {
            this.specifiedEncoding = templateSpecifiedEncoding;
            this.constructorSpecifiedEncoding = constructorSpecifiedEncoding;
        }
        
        @Override
        public String getMessage() {
            return "Encoding specified inside the template (" + specifiedEncoding
                    + ") doesn't match the encoding specified for the Template constructor"
                    + (constructorSpecifiedEncoding != null ? " (" + constructorSpecifiedEncoding + ")." : ".");
        }

        
        public String getTemplateSpecifiedEncoding() {
            return specifiedEncoding;
        }

        
        public String getConstructorSpecifiedEncoding() {
            return constructorSpecifiedEncoding;
        }

    }

}

关键代码分析

template()构造方法
   public Template(
           String name, String sourceName, Reader reader,
           Configuration cfg, ParserConfiguration customParserConfiguration,
           String encoding) throws IOException {
        this(name, sourceName, cfg, customParserConfiguration);
        
        this.setEncoding(encoding);
        LineTableBuilder ltbReader;
    
        try {
 
            ParserConfiguration actualParserConfiguration = getParserConfiguration();
            
            if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) {
                reader = new BufferedReader(reader, READER_BUFFER_SIZE);
            }
            ltbReader = new LineTableBuilder(reader, actualParserConfiguration);
            reader = ltbReader;
            
            try {
                FMParser parser = new FMParser(this, reader, actualParserConfiguration);
                if (cfg != null) {
                    _CoreAPI.setPreventStrippings(parser, cfg.getPreventStrippings());
                }
                try {
                    this.rootElement = parser.Root();
                } catch (IndexOutOfBoundsException exc) {
                    
                    if (!ltbReader.hasFailure()) {
                        throw exc;
                    }
                    rootElement = null;
                }
                this.actualTagSyntax = parser._getLastTagSyntax();
                this.interpolationSyntax = actualParserConfiguration.getInterpolationSyntax();
                this.actualNamingConvention = parser._getLastNamingConvention();
            } catch (TokenMgrError exc) {
                // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it
                // to ParseException
                throw exc.toParseException(this);
            }
        } 
 catch (ParseException e) {
            e.setTemplateName(getSourceName());
            throw e;
        } finally {
            reader.close();
        }
        
        // Throws any exception that JavaCC has silently treated as EOF:
        ltbReader.throwFailure();
        
        DebuggerService.registerTemplate(this);
        namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup);
        prefixTonamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup);
    }

getPlainTextTemplate()方法
 static public Template getPlainTextTemplate(String name, String sourceName, String content, Configuration config) {
        Template template;
        try {
//创建一个新的PlainTextTemplate(template的一个子类)//
            template = new Template(name, sourceName, new StringReader("X"), config);
        } catch (IOException e) {
            throw new BugException("Plain text template creation failed", e);
        }
        _CoreAPI.replaceText((TextBlock) template.rootElement, content);
        DebuggerService.registerTemplate(template);
        return template;
    }

 createProcessingEnvironment()方法
public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper)
    throws TemplateException, IOException {
        final TemplateHashModel dataModelHash;
        if (dataModel instanceof TemplateHashModel) {
            dataModelHash = (TemplateHashModel) dataModel;
        } else {
            if (wrapper == null) {
                wrapper = getObjectWrapper();
            }

            if (dataModel == null) {
                dataModelHash = new SimpleHash(wrapper);
            } else {
                TemplateModel wrappedDataModel = wrapper.wrap(dataModel);
                if (wrappedDataModel instanceof TemplateHashModel) {
                    dataModelHash = (TemplateHashModel) wrappedDataModel;
                } else if (wrappedDataModel == null) {
                    throw new IllegalArgumentException(
                            wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null.");
                } else {
                    
throw new IllegalArgumentException(
                            wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName()
                            + " to a TemplateHashModel. Generally, you want to use a Map or a "
                            + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean "
                            + "property names will be the variable names in the template.");
                }
            }
        }
        return new Environment(this, dataModelHash, out);
    }

getPrefixedName()方法
 //带前缀的名称,基于此模板头文件中定义的本地名称和作为参数传入的节点命名空间的ns_prefixes//
public String getPrefixedName(String localName, String nsURI) {
        if (nsURI == null || nsURI.length() == 0) {
            if (defaultNS != null) {
                return NO_NS_PREFIX + ":" + localName;
            } else {
                return localName;
            }
        } 
        if (nsURI.equals(defaultNS)) {
            return localName;
        } 
        String prefix = getPrefixForNamespace(nsURI);
        if (prefix == null) {
            return null;
        }
        return prefix + ":" + localName;
    }

containingElements()方法
//只在内部使用,稍后可能会被删除。返回:一个TemplateElements数组,包含给定的列号和行号。//
 public TreePath containingElements(int column, int line) {
        final ArrayList elements = new ArrayList();
        TemplateElement element = rootElement;
        mainloop: while (element.contains(column, line)) {
            elements.add(element);
            for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) {
                TemplateElement elem = (TemplateElement) enumeration.nextElement();
                if (elem.contains(column, line)) {
                    element = elem;
                    continue mainloop;
                }
            }
            break;
        }
        if (elements.isEmpty()) {
            return null;
        }
        return new TreePath(elements.toArray());
    }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/345170.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号