001/*license*\
002   Codelet: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com)
003
004   This software is dual-licensed under the:
005   - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version;
006   - Apache Software License (ASL) version 2.0.
007
008   Either license may be applied at your discretion. More information may be found at
009   - http://en.wikipedia.org/wiki/Multi-licensing.
010
011   The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at:
012   - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
013   - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
014\*license*/
015package  com.github.aliteralmind.codelet.type;
016   import  java.nio.file.InvalidPathException;
017   import  java.nio.file.Path;
018   import  java.nio.file.Paths;
019   import  com.github.xbn.io.PathMustBe;
020   import  com.github.aliteralmind.codelet.CodeletFormatException;
021   import  com.github.aliteralmind.codelet.CodeletInstance;
022   import  com.github.aliteralmind.codelet.CodeletType;
023   import  com.github.aliteralmind.codelet.CustomizationInstructions;
024   import  com.github.aliteralmind.codelet.TagletOfTypeProcessor;
025   import  com.github.aliteralmind.codelet.TagletTextUtil;
026   import  com.github.xbn.io.PlainTextFileUtil;
027   import  java.nio.file.AccessDeniedException;
028   import  java.nio.file.NoSuchFileException;
029   import  java.util.Iterator;
030   import  static com.github.aliteralmind.codelet.CodeletBaseConfig.*;
031   import  static com.github.xbn.lang.XbnConstants.*;
032/**
033   <p>Reads a {@link com.github.aliteralmind.codelet.CodeletType#FILE_TEXT {@.file.textlet}} taglet and outputs its replacement text.</p>
034
035 * @since  0.1.0
036 * @author  Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://codelet.aliteralmind.com">{@code http://codelet.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/codelet">{@code https://github.com/aliteralmind/codelet}</a>
037 **/
038public class FileTextProcessor extends TagletOfTypeProcessor<FileTextTemplate>  {
039   /**
040      <p>Create a new instance from an {@code CodeletInstance}.</p>
041
042    * <p>Equal to <code>{@link com.github.aliteralmind.codelet.TagletOfTypeProcessor#TagletOfTypeProcessor(CodeletType, CodeletInstance) super}(CodeletType.FILE_TEXT, instance)</code>
043    */
044   public FileTextProcessor(CodeletInstance instance) throws ClassNotFoundException, NoSuchMethodException, NoSuchFileException, AccessDeniedException  {
045      super(CodeletType.FILE_TEXT, instance);
046
047      if(getClassOrFilePortion().contains("("))  {
048         throw  new CodeletFormatException(instance, "File-text taglets cannot contain command-line parameters");
049      }
050
051      Iterator<String> fileTextLineItr = getLineIteratorFromCodeletPath(instance);
052
053//              String strSig = getStringSigForFileText();
054//              SimpleMethodSignature sig = getCustomizerSigFromString(strSig);
055
056      CustomizationInstructions<FileTextTemplate> instructions =
057         ((getCustomizerPortion() != null)
058            ?  getCustomCustomizationInstructions(CodeletType.FILE_TEXT)
059            :  newInstructionsForDefaults(
060                  new CustomizationInstructions<FileTextTemplate>(instance, CodeletType.FILE_TEXT)));
061
062      crashIfClassOrFileCannotUseCustomizer(instructions);
063
064      String fileTextCustomized = instructions.getCustomizedBody(instance, fileTextLineItr);
065
066      FileTextTemplate template = getTemplateFromInstructionsOverrideOrDefault(
067         instructions);
068
069      String finalOutput = template.fillBody(fileTextCustomized).getRendered(instance);
070      setFullyProcessedOutput(finalOutput);
071   }
072      private final Iterator<String> getLineIteratorFromCodeletPath(CodeletInstance instance)  {
073
074         boolean doDebug = isDebugOn(instance, "zzFileTextProcessor.obtainingfile");
075
076         //See CodeletInstance.FILE_TEXT
077
078         if(doDebug)  {
079            debugln("Obtaining line iterator to {@.file.textlet} file...");
080            debugln("   - Raw file path from {@.file.textlet}:  " + getClassOrFilePortion());
081            debugln("   - TagletTextUtil.getFilePath(instance): " + TagletTextUtil.getFilePath(instance));
082         }
083
084         String pathStr = getPathStrWithEnvVarPrefix(instance, doDebug);
085
086         PathMustBe pmb = new PathMustBe().existing().readable();
087
088         /*
089            First assume absolute. This also works if the path is relative to the directory in which javadoc.exe was invoked (and the file separators happen to be correct for the OS).
090          */
091         Path path = Paths.get(pathStr);
092         if(pmb.isGood(path))  {
093            if(doDebug)  {
094               debugln("   SUCCESS: Path is either absolute, or relative to the directory in which javadoc.exe was invoked (note: file-separators not yet changed).");
095            }
096
097            return  PlainTextFileUtil.getLineIterator(path.toString(), "[path to file]");
098         }
099
100
101         if(doDebug)  {
102            debugln("   Replacing all '/' file separators with \"" + FILE_SEP + "\"...");
103         }
104         if(!FILE_SEP.equals("/"))  {
105            if(doDebug)  {
106               debugln("   This system's file separator is already '/'. Nothing to change.");
107            }
108         }  else  {
109            pathStr.replace("/", FILE_SEP);
110         }
111
112         //It's not absolute.
113         /*
114            It's also not relative to the javadoc.exe-invoking directory (the current working directory: cwd) WITH THE ORIGINAL FILE-SEPARATORS. Let's try again now with the new file separators.
115          */
116         path = Paths.get(pathStr);
117         if(pmb.isGood(path))  {
118            if(doDebug)  {
119               debugln("   SUCCESS: Path is relative to the directory in which javadoc.exe was invoked.");
120            }
121
122            return  PlainTextFileUtil.getLineIterator(path.toString(), "[path to file]");
123         }
124
125         //It's not relative to the cwd. It must be relative to the enclosing
126         //file. If it's not, die.
127
128         String parentPath = instance.getEnclosingFile().getParent();
129
130         if(!parentPath.endsWith(FILE_SEP))  {
131            parentPath += FILE_SEP;
132         }
133
134         if(doDebug)  {
135            debugln("   Path to file is not absolute, and not relative to the javadoc.exe-invoking directory. It MUST be relative to the directory of its enclosing file:");
136            debugln("    - File:   " + instance.getEnclosingFile());
137            debugln("    - Parent: " + parentPath);
138         }
139
140         try  {
141            path = Paths.get(parentPath, path.toString());
142         }  catch(InvalidPathException ipx)  {
143            throw  new InvalidPathException(parentPath + path.toString(), "{@.file.textlet} path is invalid. Not absolute, not relative to javadoc.exe invoking directory, and not relative to enclosing file");
144         }
145
146         return  PlainTextFileUtil.getLineIterator(path.toString(), "[path to {@.file.textlet} file]");
147      }
148      private final String getPathStrWithEnvVarPrefix(CodeletInstance instance, boolean do_debug)  {
149
150         //See com.github.aliteralmind.codelet.CodeletType#FILE_TEXT
151
152         String rawPath = TagletTextUtil.getFilePath(instance);
153
154         if(!rawPath.startsWith("$<"))  {
155            return  rawPath;
156         }
157
158         int idxCloseCurly = rawPath.indexOf('>');
159
160         String envVarName = null;
161         try  {
162            envVarName = rawPath.substring("$<".length(), idxCloseCurly);
163         }    catch(StringIndexOutOfBoundsException sbx)  {
164            throw  new CodeletFormatException(instance, "File path begins \"$<\", but close sharp ('>') not found.", sbx);
165         }
166         String envVar = System.getenv(envVarName);
167         String sysProp = System.getProperty(envVarName);
168
169         boolean isEnvVar = (envVar != null  &&  envVar.length() > 0);
170         boolean isSysProp = (sysProp != null  &&  sysProp.length() > 0);
171
172         if(isEnvVar)  {
173            if(isSysProp)  {
174               throw  new CodeletFormatException(instance, "File path begins with \"$<" + envVarName + ">\". \"" + envVarName + "\" is both an environment variable and a system property. It must be one or the other." + LINE_SEP +
175                  "System.getenv(\"" + envVarName + "\")=\"" + envVar + "\"" + LINE_SEP +
176                  "System.getProperty(\"" + envVarName + "\")=\"" + sysProp + "\"");
177            }
178
179         }  else if(isSysProp)  {
180            envVar = sysProp;
181         }  else  {
182            throw  new CodeletFormatException(instance, "File path begins with \"$<" + envVarName + ">\". The value of both System.getenv(\"" + envVarName + "\") (" +
183               ((envVar == null) ? "null" : "\"\"") +
184               ") and System.getProperty(\"" + envVarName + "\"), (" +
185               ((sysProp == null) ? "null" : "\"\"") +
186               ") are null/empty-string");
187         }
188
189         if(do_debug)  {
190            debugln("   " +
191               (isSysProp ? "System property" : "Environment variable") +
192               " found: Name=" + envVarName + ", value=" + envVar + "");
193            debugln("   - Raw file path from {@.file.textlet}:  " + getClassOrFilePortion());
194            debugln("   - TagletTextUtil.getFilePath(instance): " + TagletTextUtil.getFilePath(instance));
195         }
196
197         return  envVar + rawPath.substring(idxCloseCurly + 1);
198      }
199   /**
200      <p>Get the string signature for a {@code {@.file.textlet}} taglets only. Except where noted, this does not validate the taglet text or the returned signature.</p>
201
202      <p>This follows the same steps as {@link com.github.aliteralmind.codelet.TagletOfTypeProcessor#getStringSignature() getStringSignature}, except<ul>
203         <li>The customizer portion of the taglet, when provided, must contain either an underscore ({@code '_'}) postfix or the processor's full function name.</li>
204         <li>The {@linkplain com.github.aliteralmind.codelet.CodeletType#getDefaultLineProcNamePrefix() default function name prefix} is {@code "getFileTextConfig_"}</li>
205      </ul></p>
206
207    * @exception  CodeletFormatException  If no function name or underscore-postfix is provided.
208    */
209   public String getStringSigForFileText()  {
210      String sig = getCustomizerPortion();
211
212      boolean doDebug = isDebugOn(getInstance(),
213         "zzTagletOfTypeProcessor.getCustomizerSigFromString");
214
215      if(sig == null)  {
216         if(doDebug)  {
217            debugln("   No Customizer");
218         }
219         return  null;
220      }
221
222      if(sig.equals("()"))  {
223         throw  new CodeletFormatException(getInstance(), "Customizer portion for a " + CodeletType.FILE_TEXT.getName() + " taglet is equal to \"()\". The processor's function name or underscore-postfix must be specified.");
224      }
225
226      return  getSig2PrnsApnddForNameOrPostfix("", sig, doDebug);
227   }
228}