| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed 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 |
| * |
| * http://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 com.intellij.compiler.server; |
| |
| import com.intellij.ProjectTopics; |
| import com.intellij.compiler.CompilerWorkspaceConfiguration; |
| import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration; |
| import com.intellij.compiler.server.impl.BuildProcessClasspathManager; |
| import com.intellij.execution.ExecutionAdapter; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.ExecutionManager; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.configurations.RunProfile; |
| import com.intellij.execution.process.*; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.PowerSaveMode; |
| import com.intellij.ide.file.BatchFileChangeListener; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.compiler.CompilationStatusListener; |
| import com.intellij.openapi.compiler.CompileContext; |
| import com.intellij.openapi.compiler.CompilerTopics; |
| import com.intellij.openapi.components.ApplicationComponent; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.EditorFactory; |
| import com.intellij.openapi.editor.event.DocumentAdapter; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; |
| import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.project.ProjectCoreUtil; |
| import com.intellij.openapi.project.ProjectManager; |
| import com.intellij.openapi.project.ProjectManagerAdapter; |
| import com.intellij.openapi.projectRoots.JavaSdk; |
| import com.intellij.openapi.projectRoots.JavaSdkType; |
| import com.intellij.openapi.projectRoots.JavaSdkVersion; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.startup.StartupManager; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.ShutDownTracker; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import com.intellij.openapi.vfs.newvfs.BulkFileListener; |
| import com.intellij.openapi.vfs.newvfs.events.VFileEvent; |
| import com.intellij.openapi.vfs.newvfs.impl.FileNameCache; |
| import com.intellij.openapi.wm.IdeFrame; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.concurrency.Semaphore; |
| import com.intellij.util.concurrency.SequentialTaskExecutor; |
| import com.intellij.util.containers.IntArrayList; |
| import com.intellij.util.io.storage.HeavyProcessLatch; |
| import com.intellij.util.messages.MessageBusConnection; |
| import com.intellij.util.net.NetUtils; |
| import gnu.trove.THashSet; |
| import io.netty.bootstrap.ServerBootstrap; |
| import io.netty.channel.Channel; |
| import io.netty.channel.ChannelInitializer; |
| import io.netty.channel.nio.NioEventLoopGroup; |
| import io.netty.handler.codec.protobuf.ProtobufDecoder; |
| import io.netty.handler.codec.protobuf.ProtobufEncoder; |
| import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; |
| import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.ide.PooledThreadExecutor; |
| import org.jetbrains.io.ChannelRegistrar; |
| import org.jetbrains.io.NettyUtil; |
| import org.jetbrains.jps.api.*; |
| import org.jetbrains.jps.cmdline.BuildMain; |
| import org.jetbrains.jps.cmdline.ClasspathBootstrap; |
| import org.jetbrains.jps.incremental.Utils; |
| import org.jetbrains.jps.model.serialization.JpsGlobalLoader; |
| |
| import javax.tools.*; |
| import java.awt.*; |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.InetSocketAddress; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| import java.util.List; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: 9/6/11 |
| */ |
| public class BuildManager implements ApplicationComponent{ |
| public static final Key<Boolean> ALLOW_AUTOMAKE = Key.create("_allow_automake_when_process_is_active_"); |
| private static final Key<String> FORCE_MODEL_LOADING_PARAMETER = Key.create(BuildParametersKeys.FORCE_MODEL_LOADING); |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager"); |
| private static final String COMPILER_PROCESS_JDK_PROPERTY = "compiler.process.jdk"; |
| public static final String SYSTEM_ROOT = "compile-server"; |
| public static final String TEMP_DIR_NAME = "_temp_"; |
| private final boolean IS_UNIT_TEST_MODE; |
| private static final String IWS_EXTENSION = ".iws"; |
| private static final String IPR_EXTENSION = ".ipr"; |
| private static final String IDEA_PROJECT_DIR_PATTERN = "/.idea/"; |
| private static final Function<String, Boolean> PATH_FILTER = |
| SystemInfo.isFileSystemCaseSensitive? |
| new Function<String, Boolean>() { |
| @Override |
| public Boolean fun(String s) { |
| return !(s.contains(IDEA_PROJECT_DIR_PATTERN) || s.endsWith(IWS_EXTENSION) || s.endsWith(IPR_EXTENSION)); |
| } |
| } : |
| new Function<String, Boolean>() { |
| @Override |
| public Boolean fun(String s) { |
| return !(StringUtil.endsWithIgnoreCase(s, IWS_EXTENSION) || StringUtil.endsWithIgnoreCase(s, IPR_EXTENSION) || StringUtil.containsIgnoreCase(s, IDEA_PROJECT_DIR_PATTERN)); |
| } |
| }; |
| |
| private final File mySystemDirectory; |
| private final ProjectManager myProjectManager; |
| |
| private final Map<RequestFuture, Project> myAutomakeFutures = Collections.synchronizedMap(new HashMap<RequestFuture, Project>()); |
| private final Map<String, RequestFuture> myBuildsInProgress = Collections.synchronizedMap(new HashMap<String, RequestFuture>()); |
| private final BuildProcessClasspathManager myClasspathManager = new BuildProcessClasspathManager(); |
| private final SequentialTaskExecutor myRequestsProcessor = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE); |
| private final Map<String, ProjectData> myProjectDataMap = Collections.synchronizedMap(new HashMap<String, ProjectData>()); |
| |
| private final BuildManagerPeriodicTask myAutoMakeTask = new BuildManagerPeriodicTask() { |
| @Override |
| protected int getDelay() { |
| return Registry.intValue("compiler.automake.trigger.delay"); |
| } |
| |
| @Override |
| protected void runTask() { |
| runAutoMake(); |
| } |
| }; |
| |
| private final BuildManagerPeriodicTask myDocumentSaveTask = new BuildManagerPeriodicTask() { |
| @Override |
| protected int getDelay() { |
| return Registry.intValue("compiler.document.save.trigger.delay"); |
| } |
| |
| private final Semaphore mySemaphore = new Semaphore(); |
| private final Runnable mySaveDocsRunnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| ((FileDocumentManagerImpl)FileDocumentManager.getInstance()).saveAllDocuments(false); |
| } |
| finally { |
| mySemaphore.up(); |
| } |
| } |
| }; |
| |
| @Override |
| public void runTask() { |
| if (shouldSaveDocuments()) { |
| mySemaphore.down(); |
| ApplicationManager.getApplication().invokeLater(mySaveDocsRunnable, ModalityState.NON_MODAL); |
| mySemaphore.waitFor(); |
| } |
| } |
| |
| private boolean shouldSaveDocuments() { |
| final Project contextProject = getCurrentContextProject(); |
| return contextProject != null && canStartAutoMake(contextProject); |
| } |
| }; |
| |
| private final ChannelRegistrar myChannelRegistrar = new ChannelRegistrar(); |
| |
| private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher(); |
| private volatile int myListenPort = -1; |
| @Nullable |
| private final Charset mySystemCharset; |
| |
| public BuildManager(final ProjectManager projectManager) { |
| final Application application = ApplicationManager.getApplication(); |
| IS_UNIT_TEST_MODE = application.isUnitTestMode(); |
| myProjectManager = projectManager; |
| mySystemCharset = CharsetToolkit.getDefaultSystemCharset(); |
| final String systemPath = PathManager.getSystemPath(); |
| File system = new File(systemPath); |
| try { |
| system = system.getCanonicalFile(); |
| } |
| catch (IOException e) { |
| LOG.info(e); |
| } |
| mySystemDirectory = system; |
| |
| projectManager.addProjectManagerListener(new ProjectWatcher()); |
| |
| final MessageBusConnection conn = application.getMessageBus().connect(); |
| conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() { |
| @Override |
| public void after(@NotNull List<? extends VFileEvent> events) { |
| if (shouldTriggerMake(events)) { |
| scheduleAutoMake(); |
| } |
| } |
| |
| private boolean shouldTriggerMake(List<? extends VFileEvent> events) { |
| if (PowerSaveMode.isEnabled()) { |
| return false; |
| } |
| |
| Project project = null; |
| ProjectFileIndex fileIndex = null; |
| |
| for (VFileEvent event : events) { |
| final VirtualFile eventFile = event.getFile(); |
| if (eventFile == null) { |
| continue; |
| } |
| if (!eventFile.isValid()) { |
| return true; // should be deleted |
| } |
| if (ProjectCoreUtil.isProjectOrWorkspaceFile(eventFile)) { |
| continue; |
| } |
| |
| if (project == null) { |
| // lazy init |
| project = getCurrentContextProject(); |
| if (project == null) { |
| return false; |
| } |
| fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); |
| } |
| |
| if (fileIndex.isInContent(eventFile)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| }); |
| |
| conn.subscribe(BatchFileChangeListener.TOPIC, new BatchFileChangeListener.Adapter() { |
| public void batchChangeStarted(Project project) { |
| cancelAutoMakeTasks(project); |
| } |
| }); |
| |
| EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() { |
| @Override |
| public void documentChanged(DocumentEvent e) { |
| scheduleProjectSave(); |
| } |
| }); |
| |
| ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { |
| @Override |
| public void run() { |
| stopListening(); |
| } |
| }); |
| } |
| |
| private List<Project> getOpenProjects() { |
| final Project[] projects = myProjectManager.getOpenProjects(); |
| if (projects.length == 0) { |
| return Collections.emptyList(); |
| } |
| final List<Project> projectList = new SmartList<Project>(); |
| for (Project project : projects) { |
| if (isValidProject(project)) { |
| projectList.add(project); |
| } |
| } |
| return projectList; |
| } |
| |
| private static boolean isValidProject(@Nullable Project project) { |
| return project != null && !project.isDisposed() && !project.isDefault() && project.isInitialized(); |
| } |
| |
| public static BuildManager getInstance() { |
| return ApplicationManager.getApplication().getComponent(BuildManager.class); |
| } |
| |
| public void notifyFilesChanged(final Collection<File> paths) { |
| doNotify(paths, false); |
| } |
| |
| public void notifyFilesDeleted(Collection<File> paths) { |
| doNotify(paths, true); |
| } |
| |
| public void runCommand(Runnable command) { |
| myRequestsProcessor.submit(command); |
| } |
| |
| private void doNotify(final Collection<File> paths, final boolean notifyDeletion) { |
| // ensure events processed in the order they arrived |
| runCommand(new Runnable() { |
| |
| @Override |
| public void run() { |
| final List<String> filtered = new ArrayList<String>(paths.size()); |
| for (File file : paths) { |
| final String path = FileUtil.toSystemIndependentName(file.getPath()); |
| if (PATH_FILTER.fun(path)) { |
| filtered.add(path); |
| } |
| } |
| if (filtered.isEmpty()) { |
| return; |
| } |
| synchronized (myProjectDataMap) { |
| if (IS_UNIT_TEST_MODE) { |
| if (notifyDeletion) { |
| LOG.info("Registering deleted paths: " + filtered); |
| } |
| else { |
| LOG.info("Registering changed paths: " + filtered); |
| } |
| } |
| for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) { |
| final ProjectData data = entry.getValue(); |
| if (notifyDeletion) { |
| data.addDeleted(filtered); |
| } |
| else { |
| data.addChanged(filtered); |
| } |
| final RequestFuture future = myBuildsInProgress.get(entry.getKey()); |
| if (future != null && !future.isCancelled() && !future.isDone()) { |
| final UUID sessionId = future.getRequestID(); |
| final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId); |
| if (channel != null) { |
| final CmdlineRemoteProto.Message.ControllerMessage message = |
| CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType( |
| CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT).setFsEvent(data.createNextEvent()).build(); |
| channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, message)); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| public static void forceModelLoading(CompileContext context) { |
| context.getCompileScope().putUserData(FORCE_MODEL_LOADING_PARAMETER, Boolean.TRUE.toString()); |
| } |
| |
| public void clearState(Project project) { |
| final String projectPath = getProjectPath(project); |
| synchronized (myProjectDataMap) { |
| final ProjectData data = myProjectDataMap.get(projectPath); |
| if (data != null) { |
| data.dropChanges(); |
| } |
| } |
| scheduleAutoMake(); |
| } |
| |
| public boolean isProjectWatched(Project project) { |
| return myProjectDataMap.containsKey(getProjectPath(project)); |
| } |
| |
| @Nullable |
| public List<String> getFilesChangedSinceLastCompilation(Project project) { |
| String projectPath = getProjectPath(project); |
| synchronized (myProjectDataMap) { |
| ProjectData data = myProjectDataMap.get(projectPath); |
| if (data != null && !data.myNeedRescan) { |
| return convertToStringPaths(data.myChanged); |
| } |
| return null; |
| } |
| } |
| |
| private static List<String> convertToStringPaths(final Collection<InternedPath> interned) { |
| final ArrayList<String> list = new ArrayList<String>(interned.size()); |
| for (InternedPath path : interned) { |
| list.add(path.getValue()); |
| } |
| return list; |
| } |
| |
| @Nullable |
| private static String getProjectPath(final Project project) { |
| final String url = project.getPresentableUrl(); |
| if (url == null) { |
| return null; |
| } |
| return VirtualFileManager.extractPath(url); |
| } |
| |
| public void scheduleAutoMake() { |
| if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) { |
| myAutoMakeTask.schedule(); |
| } |
| } |
| |
| private void scheduleProjectSave() { |
| if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) { |
| myDocumentSaveTask.schedule(); |
| } |
| } |
| |
| private void runAutoMake() { |
| final Project project = getCurrentContextProject(); |
| if (project == null || !canStartAutoMake(project)) { |
| return; |
| } |
| final List<TargetTypeBuildScope> scopes = CmdlineProtoUtil.createAllModulesScopes(false); |
| final AutoMakeMessageHandler handler = new AutoMakeMessageHandler(project); |
| final RequestFuture future = scheduleBuild( |
| project, false, true, false, scopes, Collections.<String>emptyList(), Collections.<String, String>emptyMap(), handler |
| ); |
| if (future != null) { |
| myAutomakeFutures.put(future, project); |
| try { |
| future.waitFor(); |
| } |
| finally { |
| myAutomakeFutures.remove(future); |
| } |
| } |
| } |
| |
| private static boolean canStartAutoMake(@NotNull Project project) { |
| if (project.isDisposed()) { |
| return false; |
| } |
| final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project); |
| if (!config.MAKE_PROJECT_ON_SAVE) { |
| return false; |
| } |
| if (!config.allowAutoMakeWhileRunningApplication() && hasRunningProcess(project)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Nullable |
| private Project getCurrentContextProject() { |
| return getContextProject(null); |
| } |
| |
| @Nullable |
| private Project getContextProject(@Nullable Window window) { |
| final List<Project> openProjects = getOpenProjects(); |
| if (openProjects.isEmpty()) { |
| return null; |
| } |
| if (openProjects.size() == 1) { |
| return openProjects.get(0); |
| } |
| |
| if (window == null) { |
| window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); |
| if (window == null) { |
| return null; |
| } |
| } |
| |
| Component comp = window; |
| while (true) { |
| final Container _parent = comp.getParent(); |
| if (_parent == null) { |
| break; |
| } |
| comp = _parent; |
| } |
| |
| Project project = null; |
| if (comp instanceof IdeFrame) { |
| project = ((IdeFrame)comp).getProject(); |
| } |
| if (project == null) { |
| project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(comp)); |
| } |
| |
| return isValidProject(project)? project : null; |
| } |
| |
| private static boolean hasRunningProcess(Project project) { |
| for (ProcessHandler handler : ExecutionManager.getInstance(project).getRunningProcesses()) { |
| if (!handler.isProcessTerminated() && !ALLOW_AUTOMAKE.get(handler, Boolean.FALSE)) { // active process |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public Collection<RequestFuture> cancelAutoMakeTasks(Project project) { |
| final Collection<RequestFuture> futures = new SmartList<RequestFuture>(); |
| synchronized (myAutomakeFutures) { |
| for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) { |
| if (entry.getValue().equals(project)) { |
| final RequestFuture future = entry.getKey(); |
| future.cancel(false); |
| futures.add(future); |
| } |
| } |
| } |
| return futures; |
| } |
| |
| @Nullable |
| public RequestFuture scheduleBuild( |
| final Project project, final boolean isRebuild, final boolean isMake, |
| final boolean onlyCheckUpToDate, final List<TargetTypeBuildScope> scopes, |
| final Collection<String> paths, |
| final Map<String, String> userData, final DefaultMessageHandler messageHandler) { |
| |
| final String projectPath = getProjectPath(project); |
| final UUID sessionId = UUID.randomUUID(); |
| final boolean isAutomake = messageHandler instanceof AutoMakeMessageHandler; |
| final BuilderMessageHandler handler = new MessageHandlerWrapper(messageHandler) { |
| @Override |
| public void buildStarted(UUID sessionId) { |
| super.buildStarted(sessionId); |
| try { |
| ApplicationManager.getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildStarted(project, sessionId, isAutomake); |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| } |
| } |
| |
| @Override |
| public void sessionTerminated(UUID sessionId) { |
| try { |
| super.sessionTerminated(sessionId); |
| } |
| finally { |
| try { |
| ApplicationManager.getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildFinished(project, sessionId, isAutomake); |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| } |
| } |
| } |
| }; |
| // ensure server is listening |
| if (myListenPort < 0) { |
| try { |
| synchronized (this) { |
| if (myListenPort < 0) { |
| myListenPort = startListening(); |
| } |
| } |
| } |
| catch (Exception e) { |
| handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null)); |
| handler.sessionTerminated(sessionId); |
| return null; |
| } |
| } |
| |
| try { |
| final RequestFuture<BuilderMessageHandler> future = new RequestFuture<BuilderMessageHandler>(handler, sessionId, new RequestFuture.CancelAction<BuilderMessageHandler>() { |
| @Override |
| public void cancel(RequestFuture<BuilderMessageHandler> future) throws Exception { |
| myMessageDispatcher.cancelSession(future.getRequestID()); |
| } |
| }); |
| // by using the same queue that processes events we ensure that |
| // the build will be aware of all events that have happened before this request |
| runCommand(new Runnable() { |
| @Override |
| public void run() { |
| if (future.isCancelled() || project.isDisposed()) { |
| handler.sessionTerminated(sessionId); |
| future.setDone(); |
| return; |
| } |
| |
| final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = |
| CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder() |
| .setGlobalOptionsPath(PathManager.getOptionsPath()) |
| .build(); |
| CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges; |
| final SequentialTaskExecutor projectTaskQueue; |
| synchronized (myProjectDataMap) { |
| ProjectData data = myProjectDataMap.get(projectPath); |
| if (data == null) { |
| data = new ProjectData(new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE)); |
| myProjectDataMap.put(projectPath, data); |
| } |
| if (isRebuild || (isAutomake && Registry.is("compiler.automake.force.fs.rescan"))) { |
| data.dropChanges(); |
| } |
| if (IS_UNIT_TEST_MODE) { |
| LOG.info("Scheduling build for " + |
| projectPath + |
| "; CHANGED: " + |
| new HashSet<String>(convertToStringPaths(data.myChanged)) + |
| "; DELETED: " + |
| new HashSet<String>(convertToStringPaths(data.myDeleted))); |
| } |
| currentFSChanges = data.getAndResetRescanFlag() ? null : data.createNextEvent(); |
| projectTaskQueue = data.taskQueue; |
| } |
| |
| final CmdlineRemoteProto.Message.ControllerMessage params; |
| if (isRebuild) { |
| params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, Collections.<String>emptyList(), userData, globals, null); |
| } |
| else if (onlyCheckUpToDate) { |
| params = CmdlineProtoUtil.createUpToDateCheckRequest(projectPath, scopes, paths, userData, globals, currentFSChanges); |
| } |
| else { |
| params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, isMake ? Collections.<String>emptyList() : paths, |
| userData, globals, currentFSChanges); |
| } |
| |
| myMessageDispatcher.registerBuildMessageHandler(sessionId, new MessageHandlerWrapper(handler) { |
| @Override |
| public void sessionTerminated(UUID sessionId) { |
| try { |
| super.sessionTerminated(sessionId); |
| } |
| finally { |
| future.setDone(); |
| } |
| } |
| }, params); |
| |
| try { |
| projectTaskQueue.submit(new Runnable() { |
| @Override |
| public void run() { |
| Throwable execFailure = null; |
| try { |
| if (project.isDisposed()) { |
| return; |
| } |
| myBuildsInProgress.put(projectPath, future); |
| final OSProcessHandler processHandler = launchBuildProcess(project, myListenPort, sessionId); |
| final StringBuilder stdErrOutput = new StringBuilder(); |
| processHandler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| // re-translate builder's output to idea.log |
| final String text = event.getText(); |
| if (!StringUtil.isEmptyOrSpaces(text)) { |
| LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim()); |
| if (stdErrOutput.length() < 1024 && ProcessOutputTypes.STDERR.equals(outputType)) { |
| stdErrOutput.append(text); |
| } |
| } |
| } |
| }); |
| processHandler.startNotify(); |
| final boolean terminated = processHandler.waitFor(); |
| if (terminated) { |
| final int exitValue = processHandler.getProcess().exitValue(); |
| if (exitValue != 0) { |
| final StringBuilder msg = new StringBuilder(); |
| msg.append("Abnormal build process termination: "); |
| if (stdErrOutput.length() > 0) { |
| msg.append("\n").append(stdErrOutput); |
| } |
| else { |
| msg.append("unknown error"); |
| } |
| handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null)); |
| } |
| } |
| else { |
| handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure("Disconnected from build process", null)); |
| } |
| } |
| catch (Throwable e) { |
| execFailure = e; |
| } |
| finally { |
| myBuildsInProgress.remove(projectPath); |
| if (myMessageDispatcher.getAssociatedChannel(sessionId) == null) { |
| // either the connection has never been established (process not started or execution failed), or no messages were sent from the launched process. |
| // in this case the session cannot be unregistered by the message dispatcher |
| final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId); |
| if (unregistered != null) { |
| if (execFailure != null) { |
| unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(execFailure.getMessage(), execFailure)); |
| } |
| unregistered.sessionTerminated(sessionId); |
| } |
| } |
| } |
| } |
| }); |
| } |
| catch (Throwable e) { |
| final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId); |
| if (unregistered != null) { |
| unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); |
| unregistered.sessionTerminated(sessionId); |
| } |
| } |
| } |
| }); |
| |
| return future; |
| } |
| catch (Throwable e) { |
| handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); |
| handler.sessionTerminated(sessionId); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void initComponent() { |
| } |
| |
| @Override |
| public void disposeComponent() { |
| stopListening(); |
| } |
| |
| @NotNull |
| @Override |
| public String getComponentName() { |
| return "com.intellij.compiler.server.BuildManager"; |
| } |
| |
| private OSProcessHandler launchBuildProcess(Project project, final int port, final UUID sessionId) throws ExecutionException { |
| final String compilerPath; |
| final String vmExecutablePath; |
| JavaSdkVersion sdkVersion = null; |
| |
| final String forcedCompiledJdkHome = Registry.stringValue(COMPILER_PROCESS_JDK_PROPERTY); |
| |
| if (StringUtil.isEmptyOrSpaces(forcedCompiledJdkHome)) { |
| // choosing sdk with which the build process should be run |
| Sdk projectJdk = null; |
| int sdkMinorVersion = 0; |
| |
| final Set<Sdk> candidates = new HashSet<Sdk>(); |
| final Sdk defaultSdk = ProjectRootManager.getInstance(project).getProjectSdk(); |
| if (defaultSdk != null && defaultSdk.getSdkType() instanceof JavaSdkType) { |
| candidates.add(defaultSdk); |
| } |
| |
| for (Module module : ModuleManager.getInstance(project).getModules()) { |
| final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); |
| if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) { |
| candidates.add(sdk); |
| } |
| } |
| |
| // now select the latest version from the sdks that are used in the project, but not older than the internal sdk version |
| final JavaSdk javaSdkType = JavaSdk.getInstance(); |
| for (Sdk candidate : candidates) { |
| final String vs = candidate.getVersionString(); |
| if (vs != null) { |
| final JavaSdkVersion candidateVersion = javaSdkType.getVersion(vs); |
| if (candidateVersion != null) { |
| final int candidateMinorVersion = getMinorVersion(vs); |
| if (projectJdk == null) { |
| sdkVersion = candidateVersion; |
| sdkMinorVersion = candidateMinorVersion; |
| projectJdk = candidate; |
| } |
| else { |
| final int result = candidateVersion.compareTo(sdkVersion); |
| if (result > 0 || (result == 0 && candidateMinorVersion > sdkMinorVersion)) { |
| sdkVersion = candidateVersion; |
| sdkMinorVersion = candidateMinorVersion; |
| projectJdk = candidate; |
| } |
| } |
| } |
| } |
| } |
| |
| final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); |
| if (projectJdk == null || sdkVersion == null || !sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_6)) { |
| projectJdk = internalJdk; |
| } |
| |
| // validate tools.jar presence |
| final JavaSdkType projectJdkType = (JavaSdkType)projectJdk.getSdkType(); |
| if (projectJdk.equals(internalJdk)) { |
| // important: because internal JDK can be either JDK or JRE, |
| // this is the most universal way to obtain tools.jar path in this particular case |
| final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); |
| if (systemCompiler == null) { |
| throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath."); |
| } |
| compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass()); |
| } |
| else { |
| compilerPath = projectJdkType.getToolsPath(projectJdk); |
| if (compilerPath == null) { |
| throw new ExecutionException("Cannot determine path to 'tools.jar' library for " + projectJdk.getName() + " (" + projectJdk.getHomePath() + ")"); |
| } |
| } |
| |
| vmExecutablePath = projectJdkType.getVMExecutablePath(projectJdk); |
| } |
| else { |
| compilerPath = new File(forcedCompiledJdkHome, "lib/tools.jar").getAbsolutePath(); |
| vmExecutablePath = new File(forcedCompiledJdkHome, "bin/java").getAbsolutePath(); |
| } |
| |
| final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project); |
| final GeneralCommandLine cmdLine = new GeneralCommandLine(); |
| cmdLine.setExePath(vmExecutablePath); |
| //cmdLine.addParameter("-XX:MaxPermSize=150m"); |
| //cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m"); |
| final int heapSize = config.getProcessHeapSize(JavacConfiguration.getOptions(project, JavacConfiguration.class).MAXIMUM_HEAP_SIZE); |
| |
| cmdLine.addParameter("-Xmx" + heapSize + "m"); |
| |
| if (SystemInfo.isMac && sdkVersion != null && JavaSdkVersion.JDK_1_6.equals(sdkVersion) && Registry.is("compiler.process.32bit.vm.on.mac")) { |
| // unfortunately -d32 is supported on jdk 1.6 only |
| cmdLine.addParameter("-d32"); |
| } |
| |
| cmdLine.addParameter("-Djava.awt.headless=true"); |
| cmdLine.addParameter("-Djava.endorsed.dirs=\"\""); // turn off all jre customizations for predictable behaviour |
| if (IS_UNIT_TEST_MODE) { |
| cmdLine.addParameter("-Dtest.mode=true"); |
| } |
| cmdLine.addParameter("-Djdt.compiler.useSingleThread=true"); // always run eclipse compiler in single-threaded mode |
| |
| final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION); |
| if (shouldGenerateIndex != null) { |
| cmdLine.addParameter("-D"+ GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION +"=" + shouldGenerateIndex); |
| } |
| cmdLine.addParameter("-D"+ GlobalOptions.COMPILE_PARALLEL_OPTION +"=" + Boolean.toString(config.PARALLEL_COMPILATION)); |
| cmdLine.addParameter("-D"+ GlobalOptions.REBUILD_ON_DEPENDENCY_CHANGE_OPTION + "=" + Boolean.toString(config.REBUILD_ON_DEPENDENCY_CHANGE)); |
| |
| if (Boolean.TRUE.equals(Boolean.valueOf(System.getProperty("java.net.preferIPv4Stack", "false")))) { |
| cmdLine.addParameter("-Djava.net.preferIPv4Stack=true"); |
| } |
| |
| boolean isProfilingMode = false; |
| final String additionalOptions = config.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS; |
| if (!StringUtil.isEmpty(additionalOptions)) { |
| final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false); |
| while (tokenizer.hasMoreTokens()) { |
| final String option = tokenizer.nextToken(); |
| if ("-Dprofiling.mode=true".equals(option)) { |
| isProfilingMode = true; |
| } |
| cmdLine.addParameter(option); |
| } |
| } |
| |
| if (isProfilingMode) { |
| cmdLine.addParameter("-agentlib:yjpagent=disablej2ee,disablealloc,delay=10000,sessionname=ExternalBuild"); |
| } |
| |
| // debugging |
| final int debugPort = Registry.intValue("compiler.process.debug.port"); |
| if (debugPort > 0) { |
| cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError"); |
| cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort); |
| } |
| |
| if (!Registry.is("compiler.process.use.memory.temp.cache")) { |
| cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=false"); |
| } |
| |
| // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language |
| if (mySystemCharset != null) { |
| cmdLine.setCharset(mySystemCharset); |
| cmdLine.addParameter("-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + mySystemCharset.name()); |
| } |
| cmdLine.addParameter("-D" + JpsGlobalLoader.FILE_TYPES_COMPONENT_NAME_KEY + "=" + FileTypeManagerImpl.getFileTypeComponentName()); |
| for (String name : new String[]{"user.language", "user.country", "user.region", PathManager.PROPERTY_PATHS_SELECTOR}) { |
| final String value = System.getProperty(name); |
| if (value != null) { |
| cmdLine.addParameter("-D" + name + "=" + value); |
| } |
| } |
| cmdLine.addParameter("-D" + PathManager.PROPERTY_HOME_PATH + "=" + PathManager.getHomePath()); |
| cmdLine.addParameter("-D" + PathManager.PROPERTY_CONFIG_PATH + "=" + PathManager.getConfigPath()); |
| cmdLine.addParameter("-D" + PathManager.PROPERTY_PLUGINS_PATH + "=" + PathManager.getPluginsPath()); |
| |
| cmdLine.addParameter("-D" + GlobalOptions.LOG_DIR_OPTION + "=" + FileUtil.toSystemIndependentName(getBuildLogDirectory().getAbsolutePath())); |
| |
| final File workDirectory = getBuildSystemDirectory(); |
| //noinspection ResultOfMethodCallIgnored |
| workDirectory.mkdirs(); |
| cmdLine.addParameter("-Djava.io.tmpdir=" + FileUtil.toSystemIndependentName(workDirectory.getPath()) + "/" + TEMP_DIR_NAME); |
| |
| for (BuildProcessParametersProvider provider : project.getExtensions(BuildProcessParametersProvider.EP_NAME)) { |
| final List<String> args = provider.getVMArguments(); |
| cmdLine.addParameters(args); |
| } |
| |
| @SuppressWarnings("UnnecessaryFullyQualifiedName") |
| final Class<?> launcherClass = org.jetbrains.jps.cmdline.Launcher.class; |
| |
| final List<String> launcherCp = new ArrayList<String>(); |
| launcherCp.add(ClasspathBootstrap.getResourcePath(launcherClass)); |
| launcherCp.add(compilerPath); |
| ClasspathBootstrap.appendJavaCompilerClasspath(launcherCp); |
| launcherCp.addAll(BuildProcessClasspathManager.getLauncherClasspath(project)); |
| cmdLine.addParameter("-classpath"); |
| cmdLine.addParameter(classpathToString(launcherCp)); |
| |
| cmdLine.addParameter(launcherClass.getName()); |
| |
| final List<String> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath(true); |
| cp.addAll(myClasspathManager.getBuildProcessPluginsClasspath(project)); |
| if (isProfilingMode) { |
| cp.add(new File(workDirectory, "yjp-controller-api-redist.jar").getPath()); |
| } |
| cmdLine.addParameter(classpathToString(cp)); |
| |
| cmdLine.addParameter(BuildMain.class.getName()); |
| cmdLine.addParameter("127.0.0.1"); |
| cmdLine.addParameter(Integer.toString(port)); |
| cmdLine.addParameter(sessionId.toString()); |
| |
| cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath())); |
| |
| cmdLine.setWorkDirectory(workDirectory); |
| |
| final Process process = cmdLine.createProcess(); |
| |
| return new OSProcessHandler(process, null, mySystemCharset) { |
| @Override |
| protected boolean shouldDestroyProcessRecursively() { |
| return true; |
| } |
| }; |
| } |
| |
| public File getBuildSystemDirectory() { |
| return new File(mySystemDirectory, SYSTEM_ROOT); |
| } |
| |
| public File getBuildLogDirectory() { |
| return new File(PathManager.getLogPath(), "build-log"); |
| } |
| |
| @Nullable |
| public File getProjectSystemDirectory(Project project) { |
| final String projectPath = getProjectPath(project); |
| return projectPath != null? Utils.getDataStorageRoot(getBuildSystemDirectory(), projectPath) : null; |
| } |
| |
| private static int getMinorVersion(String vs) { |
| final int dashIndex = vs.lastIndexOf('_'); |
| if (dashIndex >= 0) { |
| StringBuilder builder = new StringBuilder(); |
| for (int idx = dashIndex + 1; idx < vs.length(); idx++) { |
| final char ch = vs.charAt(idx); |
| if (Character.isDigit(ch)) { |
| builder.append(ch); |
| } |
| else { |
| break; |
| } |
| } |
| if (builder.length() > 0) { |
| try { |
| return Integer.parseInt(builder.toString()); |
| } |
| catch (NumberFormatException ignored) { |
| } |
| } |
| } |
| return 0; |
| } |
| |
| public void stopListening() { |
| myChannelRegistrar.close(); |
| } |
| |
| private int startListening() throws Exception { |
| final ServerBootstrap bootstrap = NettyUtil.nioServerBootstrap(new NioEventLoopGroup(1, PooledThreadExecutor.INSTANCE)); |
| bootstrap.childHandler(new ChannelInitializer() { |
| @Override |
| protected void initChannel(Channel channel) throws Exception { |
| channel.pipeline().addLast(myChannelRegistrar, |
| new ProtobufVarint32FrameDecoder(), |
| new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()), |
| new ProtobufVarint32LengthFieldPrepender(), |
| new ProtobufEncoder(), |
| myMessageDispatcher); |
| } |
| }); |
| Channel serverChannel = bootstrap.bind(NetUtils.getLoopbackAddress(), 0).syncUninterruptibly().channel(); |
| myChannelRegistrar.add(serverChannel); |
| return ((InetSocketAddress)serverChannel.localAddress()).getPort(); |
| } |
| |
| private static String classpathToString(List<String> cp) { |
| StringBuilder builder = new StringBuilder(); |
| for (String file : cp) { |
| if (builder.length() > 0) { |
| builder.append(File.pathSeparator); |
| } |
| builder.append(FileUtil.toCanonicalPath(file)); |
| } |
| return builder.toString(); |
| } |
| |
| private static abstract class BuildManagerPeriodicTask implements Runnable { |
| private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); |
| private final AtomicBoolean myInProgress = new AtomicBoolean(false); |
| private final Runnable myTaskRunnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| runTask(); |
| } |
| finally { |
| myInProgress.set(false); |
| } |
| } |
| }; |
| |
| public final void schedule() { |
| myAlarm.cancelAllRequests(); |
| final int delay = Math.max(100, getDelay()); |
| myAlarm.addRequest(this, delay); |
| } |
| |
| protected abstract int getDelay(); |
| |
| protected abstract void runTask(); |
| |
| @Override |
| public final void run() { |
| if (!HeavyProcessLatch.INSTANCE.isRunning() && !myInProgress.getAndSet(true)) { |
| try { |
| ApplicationManager.getApplication().executeOnPooledThread(myTaskRunnable); |
| } |
| catch (RejectedExecutionException ignored) { |
| // we were shut down |
| myInProgress.set(false); |
| } |
| catch (Throwable e) { |
| myInProgress.set(false); |
| throw new RuntimeException(e); |
| } |
| } |
| else { |
| schedule(); |
| } |
| } |
| } |
| |
| private class ProjectWatcher extends ProjectManagerAdapter { |
| private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>(); |
| |
| @Override |
| public void projectOpened(final Project project) { |
| final MessageBusConnection conn = project.getMessageBus().connect(); |
| myConnections.put(project, conn); |
| conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { |
| @Override |
| public void rootsChanged(final ModuleRootEvent event) { |
| final Object source = event.getSource(); |
| if (source instanceof Project) { |
| clearState((Project)source); |
| } |
| } |
| }); |
| conn.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() { |
| @Override |
| public void processTerminated(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) { |
| scheduleAutoMake(); |
| } |
| }); |
| conn.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() { |
| private final Set<String> myRootsToRefresh = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY); |
| @Override |
| public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) { |
| final String[] roots; |
| synchronized (myRootsToRefresh) { |
| roots = ArrayUtil.toStringArray(myRootsToRefresh); |
| myRootsToRefresh.clear(); |
| } |
| ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { |
| @Override |
| public void run() { |
| if (project.isDisposed()) { |
| return; |
| } |
| final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); |
| final LocalFileSystem lfs = LocalFileSystem.getInstance(); |
| final Set<VirtualFile> filesToRefresh = new HashSet<VirtualFile>(); |
| for (String root : roots) { |
| final VirtualFile rootFile = lfs.refreshAndFindFileByPath(root); |
| if (rootFile != null && fileIndex.isInSourceContent(rootFile)) { |
| filesToRefresh.add(rootFile); |
| } |
| } |
| if (!filesToRefresh.isEmpty()) { |
| lfs.refreshFiles(filesToRefresh, true, true, null); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void fileGenerated(String outputRoot, String relativePath) { |
| synchronized (myRootsToRefresh) { |
| myRootsToRefresh.add(outputRoot); |
| } |
| } |
| }); |
| final String projectPath = getProjectPath(project); |
| Disposer.register(project, new Disposable() { |
| @Override |
| public void dispose() { |
| myProjectDataMap.remove(projectPath); |
| } |
| }); |
| StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() { |
| @Override |
| public void run() { |
| scheduleAutoMake(); // run automake after project opened |
| } |
| }); |
| } |
| |
| @Override |
| public boolean canCloseProject(Project project) { |
| cancelAutoMakeTasks(project); |
| return super.canCloseProject(project); |
| } |
| |
| @Override |
| public void projectClosing(Project project) { |
| for (RequestFuture future : cancelAutoMakeTasks(project)) { |
| future.waitFor(500, TimeUnit.MILLISECONDS); |
| } |
| } |
| |
| @Override |
| public void projectClosed(Project project) { |
| myProjectDataMap.remove(getProjectPath(project)); |
| final MessageBusConnection conn = myConnections.remove(project); |
| if (conn != null) { |
| conn.disconnect(); |
| } |
| } |
| } |
| |
| private static class ProjectData { |
| final SequentialTaskExecutor taskQueue; |
| private final Set<InternedPath> myChanged = new THashSet<InternedPath>(); |
| private final Set<InternedPath> myDeleted = new THashSet<InternedPath>(); |
| private long myNextEventOrdinal = 0L; |
| private boolean myNeedRescan = true; |
| |
| private ProjectData(SequentialTaskExecutor taskQueue) { |
| this.taskQueue = taskQueue; |
| } |
| |
| public void addChanged(Collection<String> paths) { |
| if (!myNeedRescan) { |
| for (String path : paths) { |
| final InternedPath _path = InternedPath.create(path); |
| myDeleted.remove(_path); |
| myChanged.add(_path); |
| } |
| } |
| } |
| |
| public void addDeleted(Collection<String> paths) { |
| if (!myNeedRescan) { |
| for (String path : paths) { |
| final InternedPath _path = InternedPath.create(path); |
| myChanged.remove(_path); |
| myDeleted.add(_path); |
| } |
| } |
| } |
| |
| public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() { |
| final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder = |
| CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder(); |
| builder.setOrdinal(++myNextEventOrdinal); |
| |
| for (InternedPath path : myChanged) { |
| builder.addChangedPaths(path.getValue()); |
| } |
| myChanged.clear(); |
| |
| for (InternedPath path : myDeleted) { |
| builder.addDeletedPaths(path.getValue()); |
| } |
| myDeleted.clear(); |
| |
| return builder.build(); |
| } |
| |
| public boolean getAndResetRescanFlag() { |
| final boolean rescan = myNeedRescan; |
| myNeedRescan = false; |
| return rescan; |
| } |
| |
| public void dropChanges() { |
| myNeedRescan = true; |
| myNextEventOrdinal = 0L; |
| myChanged.clear(); |
| myDeleted.clear(); |
| } |
| } |
| |
| private static abstract class InternedPath { |
| protected final int[] myPath; |
| |
| /** |
| * @param path assuming system-independent path with forward slashes |
| */ |
| protected InternedPath(String path) { |
| final IntArrayList list = new IntArrayList(); |
| final StringTokenizer tokenizer = new StringTokenizer(path, "/", false); |
| while(tokenizer.hasMoreTokens()) { |
| final String element = tokenizer.nextToken(); |
| list.add(FileNameCache.storeName(element)); |
| } |
| myPath = list.toArray(); |
| } |
| |
| public abstract String getValue(); |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| InternedPath path = (InternedPath)o; |
| |
| if (!Arrays.equals(myPath, path.myPath)) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Arrays.hashCode(myPath); |
| } |
| |
| public static InternedPath create(String path) { |
| return path.startsWith("/")? new XInternedPath(path) : new WinInternedPath(path); |
| } |
| } |
| |
| private static class WinInternedPath extends InternedPath { |
| private WinInternedPath(String path) { |
| super(path); |
| } |
| |
| @Override |
| public String getValue() { |
| if (myPath.length == 1) { |
| final String name = FileNameCache.getVFileName(myPath[0]).toString(); |
| // handle case of windows drive letter |
| return name.length() == 2 && name.endsWith(":")? name + "/" : name; |
| } |
| |
| final StringBuilder buf = new StringBuilder(); |
| for (int element : myPath) { |
| if (buf.length() > 0) { |
| buf.append("/"); |
| } |
| buf.append(FileNameCache.getVFileName(element)); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| private static class XInternedPath extends InternedPath { |
| private XInternedPath(String path) { |
| super(path); |
| } |
| |
| @Override |
| public String getValue() { |
| if (myPath.length > 0) { |
| final StringBuilder buf = new StringBuilder(); |
| for (int element : myPath) { |
| buf.append("/").append(FileNameCache.getVFileName(element)); |
| } |
| return buf.toString(); |
| } |
| return "/"; |
| } |
| } |
| |
| } |