I have a file (more specifically, a log4j configuration file) and I want to be able to read in the file and pick out certain lines in the code and replace them. For example, within the file there is a string of text that indicates the directory it is stored in, or the level of the logger. I want to be able to replace those string of text without reading in the file, writing it to another file, and deleting the original file. Is there a more efficient way of doing find and replace texts in a file using Java?
Here is an example of the text file I'm trying to work with:
log4j.rootLogger=DEBUG, A0
log4j.appender.A0=org.apache.log4j.RollingFileAppender
log4j.appender.A0.File=C:/log.txt
log4j.appender.A0.MaxFileSize=100KB
log4j.appender.A0.MaxBackupIndex=1
log4j.appender.A0.layout=org.apache.log4j.RollingFileAppender
log4j.appender.A0.layout.ConversionPattern=%-4r [%t] %-5p: %c %x - %m%n
I want to be able to read the file and replace 'DEBUG' with another level or replace the file directory name 'C:/log.txt'. The log configuration file is also written in xml. An example of that is featured below.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<appender class="org.apache.log4j.RollingFileAppender" name="A0">
<param name="append" value="false"/>
<param name="File" value="C:/log/.txt"/>
<param name="MaxBackupIndex" value="1"/>
<param name="MaxFileSize" value="100KB"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-4r [%t] %-5p: %c %x - %m%n"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="A0"/>
</root>
</log4j:configuration>
I'm thinking it may be possible to use a hash map for this type of implementation?
Any decent text editor has a search&replace facility that supports regular expressions.
If however, you have reason to reinvent the wheel in Java, you can do:
Path path = Paths.get("test.txt");
Charset charset = StandardCharsets.UTF_8;
String content = new String(Files.readAllBytes(path), charset);
content = content.replaceAll("foo", "bar");
Files.write(path, content.getBytes(charset));
This only works for Java 7 or newer. If you are stuck on an older Java, you can do:
String content = IOUtils.toString(new FileInputStream(myfile), myencoding);
content = content.replaceAll(myPattern, myReplacement);
IOUtils.write(content, new FileOutputStream(myfile), myencoding);
In this case, you'll need to add error handling and close the streams after you are done with them.
IOUtils is documented at http://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/IOUtils.html
After visiting this question and noting the initial concerns of the chosen solution, I figured I'd contribute this one for those not using Java 7 which uses FileUtils instead of IOUtils from Apache Commons. The advantage here is that the readFileToString and the writeStringToFile handle the issue of closing the files for you automatically. (writeStringToFile doesn't document it but you can read the source). Hopefully this recipe simplifies things for anyone new coming to this problem.
try {
String content = FileUtils.readFileToString(new File("InputFile"), "UTF-8");
content = content.replaceAll("toReplace", "replacementString");
File tempFile = new File("OutputFile");
FileUtils.writeStringToFile(tempFile, content, "UTF-8");
} catch (IOException e) {
//Simple exception handling, replace with what's necessary for your use case!
throw new RuntimeException("Generating file failed", e);
}
public static void replaceFileString(String old, String new) throws IOException {
String fileName = Settings.getValue("fileDirectory");
FileInputStream fis = new FileInputStream(fileName);
String content = IOUtils.toString(fis, Charset.defaultCharset());
content = content.replaceAll(old, new);
FileOutputStream fos = new FileOutputStream(fileName);
IOUtils.write(content, new FileOutputStream(fileName), Charset.defaultCharset());
fis.close();
fos.close();
}
above is my implementation of Meriton's example that works for me. The fileName is the directory (ie. D:\utilities\settings.txt). I'm not sure what character set should be used, but I ran this code on a Windows XP machine just now and it did the trick without doing that temporary file creation and renaming stuff.
You might want to use Scanner to parse through and find the specific sections you want to modify. There's also Split and StringTokenizer that may work, but at the level you're working at Scanner might be what's needed.
Here's some additional info on what the difference is between them:
Scanner vs. StringTokenizer vs. String.Split
This is the sort of thing I'd normally use a scripting language for. It's very useful to have the ability to perform these sorts of transformations very simply using something like Ruby/Perl/Python (insert your favorite scripting language here).
I wouldn't normally use Java for this since it's too heavyweight in terms of development cycle/typing etc.
Note that if you want to be particular in manipulating XML, it's advisable to read the file as XML and manipulate it as such (the above scripting languages have very useful and simple APIs for doing this sort of work). A simple text search/replace can invalidate your file in terms of character encoding etc. As always, it depends on the complexity of your search/replace requirements.
You can use Java's Scanner class to parse words of a file and process them in your application, and then use a BufferedWriter or FileWriter to write back to the file, applying the changes.
I think there is a more efficient way of getting the iterator's position of the scanner at some point, in order to better implement editting. But since files are either open for reading, or writing, I'm not sure regarding that.
In any case, you can use libraries already available for parsing of XML files, which have all of this implemented already and will allow you to do what you want easily.
Related
I supply a formatted java string with significant whitespace
String.format("%1$12s %2$15s", "someString", "anotherOne");
to be logged using the following conversion pattern in log4j
<param name="ConversionPattern" value="%d{ISO8601} %-5p [%t] %c{1} %m%n" />
In the logs, I receive the variable with just a single preceding whitespace.
... PatternStuff ... someString anotherOne
slf4j version 1.7.3
log4j version 1.2.14
Is it possible to show the whitespace added by String.format? Some other conversion pattern variable other than %m?
My workplace has an internal web-based logviewer that quickly gives developers a way to access the contents of log files. We have the ability to print logs out in a generated webpage or to download the log. When I view them in the browser, the spaces are collapsed (because of standard HTML output). However, when I download the log files, they appear with the given formatting! Sorry for the confusion.
What I'm trying to do :
Generate on the server a txt file and download it on the client side.
I'm using struts 2, here are the code parts :
AwesomeAction.java
InputStream fileInputStream;
public InputStream getFileInputStream(){
return fileInputStream;
}
public String execute() {
res = "toto";
fileInputStream = new StringInputStream(res);
return SUCCESS;
}
struts.xml
<action name="awesomeAction" class="pathtomyawesomeaction">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">fileInputStream</param>
<param name="contentDisposition">attachment;filename="id_opp.txt"</param>
<param name="bufferSize">1024</param>
</result>
<result name="error" type="redirect">/erreur.do</result>
</action>
What is not working :
When I click on the link triggering the action, a file named "id_opp.txt" is actually downloaded, it contains all the text ("toto") but it adds a whitespace before each character.
" t o t o "
With server debugs, I'm sure that my variable contains "toto" server side, so it must be a config that I miss...
Any idea ?
Using import org.hsqldb.lib.StringInputStream; for the InputStream, since the String is built in that class, I can't use a FileInputStream or anything, I'm not aware of any other way to make that ?
Checking on the encoding, will update as soon as I got some results
Resolved thanks to Thomas :
Using the StringInputStream was the root of the problem, instead switched it to :
fileInputStream = new ByteArrayInputStream(res.getBytes(StandardCharsets.UTF_8));
Which build an inputStream for the "res" variable, with the actual encoding etc...
Problems like this might occur due to different encodings. Internally Java stores strings using 16-bit characters and when you convert those into a byte representation (e.g. for writing to a stream) it will use some encoding (either one the caller provides or the default encoding which often is the system encoding).
Thus it would depend on what StringInputStream is doing with the string, i.e. how it converts the string to bytes and which encoding is used (if any).
Additionally it might depend on the reader on how the txt file is interpreted if you don't add any information to indicate the encoding (like the BOM (byte order mark) for UTF-8).
Doing it as you did, i.e. using ByteArrayInputStream(res.getBytes(StandardCharsets.UTF_8)), would at least solve the problem when writing. Editors might then interpret the data correctly, even if the BOM is missing (and UTF-8 represents common characters like ISO-Latin 1 (ASCII) and thus even the "wrong" encoding in the reader might not be a problem).
I have created a simple XML file to which I am writing some tweets that i collect on every batch run. I want the file to be appended rather than rewritten on every batch run. At the moment it is overwriting the current document on every batch run.
Can help me out with this please !!
Use this:
FileWriter writer = new FileWriter(file,true);
that will open the file in append mode.
Please note that it might cause a problem, if you have:
<root>
<sometag></sometag>
</root>
and then you append the same structure again, the next time you read the file you'll get:
<root>
<sometag></sometag>
</root>
<root>
<sometag></sometag>
</root>
which is not valid. So if you need to parse this xml later, you'll have to do it this way:
Read entire xml content.
Add new element to document object (using DOM parser)
write updated document to file (overriding old data).
I am writing an application that must update parts of an already existing xml file based on a set of files in a directory. An example of this xml file can be seen below:
http://izpack.org/documentation/sample-install-definition.html
In the below scope a list of files is added and its specified if they should be "parsable" (used for parameter substitution):
<packs>
<pack name="Main Application" required="yes" installGroups="New Application" >
<file src="post-install-tasks.bat" targetdir="$INSTALL_PATH"/>
<file src="build.xml" targetdir="$INSTALL_PATH"/>
<parsable targetfile="$INSTALL_PATH/post-install-tasks.bat"/>
<parsable targetfile="$INSTALL_PATH/build.xml"/>
</pack>
</packs>
Now the number of files that must be added to this scope can change each time the application is run. To make this possible I have considered the following approach:
1) Read the whole xml into a org.w3c.dom.*; Document and add nodes based on result from reading the directory.
2) Somehow add the content from a .properties file to the scope. This way its possible to update the filelist without recompiling the code.
3) ??
Any suggestions on a good approach to this kind of task?
if there's a chance that your XML configuration might be of significant size, then it is really not good to go ahead with a DOM based approach [due to the associated memory footprint of loading a large XML document]
you should take a look at StaX. it has a highly optimised approach for both parsing and writing XML documents.
3) Overwrite the old file with your new, modified version. The DOM parsers keep comments intact, but you could end up with formatting differences. In order to write to a file, do:
Source source = new DOMSource(doc);
File file = new File(filename);
Result result = new StreamResult(file);
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.transform(source, result);
I need your expertise once again. I have a java class that searches a directory for xml files (displays the files it finds in the eclipse console window), applies the specified xslt to these and sends the output to a directory.
What I want to do now is create an xml containing the file names and file format types. The format should be something like;
<file>
<fileName> </fileName>
<fileType> </fileType>
</file>
<file>
<fileName> </fileName>
<fileType> </fileType>
</file>
Where for every file it finds in the directory it creates a new <file>.
Any help is truely appreciated.
Use an XML library. There are plenty around, and the third party ones are almost all easier to use than the built-in DOM API in Java. Last time I used it, JDom was pretty good. (I haven't had to do much XML recently.)
Something like:
Element rootElement = new Element("root"); // You didn't show what this should be
Document document = new Document(rootElement);
for (Whatever file : files)
{
Element fileElement = new Element("file");
fileElement.addContent(new Element("fileName").addContent(file.getName());
fileElement.addContent(new Element("fileType").addContent(file.getType());
}
String xml = XMLOutputter.outputString(document);
Have a look at DOM and ECS. The following example was adapted to you requirements from here:
XMLDocument document = new XMLDocument();
for (File f : files) {
document.addElement( new XML("file")
.addXMLAttribute("fileName", file.getName())
.addXMLAttribute("fileType", file.getType())
)
);
}
You can use the StringBuilder approach suggested by Vinze, but one caveat is that you will need to make sure your filenames contain no special XML characters, and escape them if they do (for example replace < with <, and deal with quotes appropriately).
In this case it probably doesn't arise and you will get away without it, however if you ever port this code to reuse in another case, you may be bitten by this. So you might want to look at an XMLWriter class which will do all the escaping work for you.
Well just use a StringBuilder :
StringBuilder builder = new StringBuilder();
for(File f : files) {
builder.append("<file>\n\t<fileName>").append(f.getName).append("</fileName>\n)";
[...]
}
System.out.println(builder.toString());