/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.core.util; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.Parser; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.function.Function; import javax.annotation.Nullable; import org.apache.commons.io.IOUtils; /** * Utility to read and write Protocol Buffers messages */ public class Protobuf { private Protobuf() { // only static stuff } /** * Returns the message contained in {@code file}. Throws an unchecked exception * if the file does not exist, is empty or does not contain message with the * expected type. */ public static <MSG extends Message> MSG read(File file, Parser<MSG> parser) { InputStream input = null; try { input = new BufferedInputStream(new FileInputStream(file)); return parser.parseFrom(input); } catch (Exception e) { throw ContextException.of("Unable to read message", e).addContext("file", file); } finally { IOUtils.closeQuietly(input); } } public static <MSG extends Message> MSG read(InputStream input, Parser<MSG> parser) { try { return parser.parseFrom(input); } catch (Exception e) { throw ContextException.of("Unable to read message", e); } finally { IOUtils.closeQuietly(input); } } /** * Writes a single message to {@code file}. Existing content is replaced, the message is not * appended. */ public static void write(Message message, File toFile) { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(toFile, false)); message.writeTo(out); } catch (Exception e) { throw ContextException.of("Unable to write message", e).addContext("file", toFile); } finally { IOUtils.closeQuietly(out); } } /** * Streams multiple messages to {@code file}. Reading the messages back requires to * call methods {@code readStream(...)}. * <p> * See https://developers.google.com/protocol-buffers/docs/techniques#streaming * </p> */ public static <MSG extends Message> void writeStream(Iterable<MSG> messages, File toFile, boolean append) { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(toFile, append)); writeStream(messages, out); } catch (Exception e) { throw ContextException.of("Unable to write messages", e).addContext("file", toFile); } finally { IOUtils.closeQuietly(out); } } /** * Streams multiple messages to {@code output}. Reading the messages back requires to * call methods {@code readStream(...)}. * <p> * See https://developers.google.com/protocol-buffers/docs/techniques#streaming * </p> */ public static <MSG extends Message> void writeStream(Iterable<MSG> messages, OutputStream output) { try { for (Message message : messages) { message.writeDelimitedTo(output); } } catch (Exception e) { throw ContextException.of("Unable to write messages", e); } } /** * Reads a stream of messages. This method returns an empty iterator if there are no messages. An * exception is raised on IO error, if file does not exist or if messages have a * different type than {@code parser}. */ public static <MSG extends Message> CloseableIterator<MSG> readStream(File file, Parser<MSG> parser) { try { // the input stream is closed by the CloseableIterator BufferedInputStream input = new BufferedInputStream(new FileInputStream(file)); return readStream(input, parser); } catch (Exception e) { throw ContextException.of("Unable to read messages", e).addContext("file", file); } } /** * Reads a stream of messages. This method returns an empty iterator if there are no messages. An * exception is raised on IO error or if messages have a different type than {@code parser}. * <p> * The stream is not closed by this method. It is closed when {@link CloseableIterator} traverses * all messages or when {@link CloseableIterator#close()} is called. * </p> */ public static <MSG extends Message> CloseableIterator<MSG> readStream(InputStream input, Parser<MSG> parser) { // the stream is closed by the CloseableIterator return new StreamIterator<>(parser, input); } private static class StreamIterator<MSG extends Message> extends CloseableIterator<MSG> { private final Parser<MSG> parser; private final InputStream input; private StreamIterator(Parser<MSG> parser, InputStream input) { this.parser = parser; this.input = input; } @Override protected MSG doNext() { try { return parser.parsePartialDelimitedFrom(input); } catch (InvalidProtocolBufferException e) { throw ContextException.of(e); } } @Override protected void doClose() { IOUtils.closeQuietly(input); } } /** * Call a setter method of {@link com.google.protobuf.GeneratedMessage.Builder} if the parameter * is not null. Do nothing if parameter is null. * <br/> * This utility method is convenient as the setter methods of Protobuf 2 do not accept * {@code null} parameters. It avoid increasing complexity with "if not null" conditions. * <br/> * Example: * <pre> * setNullable(dto.getLine(), issueBuilder::setLine); * </pre> */ public static <PARAM> void setNullable(@Nullable PARAM parameter, Function<PARAM, ?> setter) { if (parameter != null) { setter.apply(parameter); } } /** * Same as {@link #setNullable(Object, Function)} but the parameter is converted by the function "{@code paramConverter}" * before being applied to setter. If the converter returns {@code null}, then setter method * is not called. * <br/> * Example: * <pre> * setNullable(dto.getIssueCreationDate(), issueBuilder::setCreationDate, DateUtils::formatDateTime); * </pre> * @see #setNullable(Object, Function) */ public static <PARAM, PARAM2> void setNullable(@Nullable PARAM param, Function<PARAM2, ?> setter, Function<PARAM, PARAM2> paramConverter) { if (param != null) { PARAM2 output = paramConverter.apply(param); setNullable(output, setter); } } }